import React, { useState, useEffect, useRef, useCallback } from 'react';
import styled, { css } from 'styled-components';
import getFileDownloadUrl from '@/api/server/files/getFileDownloadUrl';
import getFileStreamUrl from './api/server/files/getFileStreamUrl';
import Spinner from '@/components/elements/Spinner';
import { Dialog } from '@/components/elements/dialog';
import { Button } from '@/components/elements/button/index';
import Tooltip from '@/components/elements/tooltip/Tooltip';
import Input from '@/components/elements/Input';
import GreyRowBox from '@/components/elements/GreyRowBox';
import { colors } from './colors';
import { themeColors } from './colorExtractor';

const cx = (...parts: Array<string | false | null | undefined>) => parts.filter(Boolean).join(' ');

const drawRoundRect = (
    ctx: CanvasRenderingContext2D,
    x: number,
    y: number,
    width: number,
    height: number,
    radii: number | number[]
) => {
    // @ts-ignore
    if (typeof ctx.roundRect === 'function') {
        // @ts-ignore
        ctx.roundRect(x, y, width, height, radii);
        return;
    }

    const [tl, tr, br, bl] = Array.isArray(radii)
        ? radii
        : [radii, radii, radii, radii];

    ctx.moveTo(x + tl, y);
    ctx.lineTo(x + width - tr, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + tr);
    ctx.lineTo(x + width, y + height - br);
    ctx.quadraticCurveTo(x + width, y + height, x + width - br, y + height);
    ctx.lineTo(x + bl, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - bl);
    ctx.lineTo(x, y + tl);
    ctx.quadraticCurveTo(x, y, x + tl, y);
};

const VOLUME_STORAGE_KEY = 'betterfiles.media.volume';
const clampVolume = (value: number) => Math.min(1, Math.max(0, value));
const readStoredVolume = (): number => {
    if (typeof window === 'undefined') return 1;
    try {
        const raw = window.localStorage.getItem(VOLUME_STORAGE_KEY);
        if (!raw) return 1;
        const parsed = parseFloat(raw);
        if (Number.isNaN(parsed)) return 1;
        return clampVolume(parsed);
    } catch {
        return 1;
    }
};
const writeStoredVolume = (value: number) => {
    if (typeof window === 'undefined') return;
    try {
        window.localStorage.setItem(VOLUME_STORAGE_KEY, clampVolume(value).toString());
    } catch {

    }
};

const globalAudioRegistry: Map<string, {
    play: () => void;
    pause: () => void;
    seek: (time: number) => void;
    skip: (delta: number) => void;
    isPlaying: () => boolean;
    getCurrentTime: () => number;
}> = new Map();

const syncAllAudio = (action: 'play' | 'pause' | 'toggle') => {
    const players = Array.from(globalAudioRegistry.values());
    if (action === 'toggle') {
        const anyPlaying = players.some(p => p.isPlaying());
        action = anyPlaying ? 'pause' : 'play';
    }
    players.forEach(p => action === 'play' ? p.play() : p.pause());
};

const syncSeekAll = (time: number) => {
    globalAudioRegistry.forEach(p => p.seek(time));
};

const syncSkipAll = (delta: number) => {
    globalAudioRegistry.forEach(p => p.skip(delta));
};

const ViewerWrapper = styled.div`
    flex: 1;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
`;

const ViewerContainer = styled(GreyRowBox)`
    width: 100%;
    height: 100%;
    padding: 0;
    overflow: hidden;
    display: flex;
    align-items: stretch;
    justify-content: stretch;
    position: relative;
    background: ${themeColors.page.secondary};
    border-color: ${themeColors.page.secondaryHover};
`;

const Image = styled.img`
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
`;

const PlayerContainer = styled.div`
    position: relative;
    width: 100%;
    height: 100%;
    flex: 1;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: transparent;
    border-radius: inherit;
`;

const VideoElement = styled.video`
    width: 100%;
    height: 100%;
    object-fit: contain;
`;

const ControlsOverlay = styled.div<{ visible: boolean }>`
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    transition: opacity 300ms;
    opacity: ${props => props.visible ? 1 : 0};
    background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.35) 55%, rgba(0, 0, 0, 0.65) 100%);
    pointer-events: none;
`;

const ControlsBar = styled.div`
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1rem;
    width: 100%;
    user-select: none;
    pointer-events: auto;
`;

const ControlButton = styled.button.attrs({ className: 'text-white hover:bg-gray-600' })`
    padding: 0.5rem;
    border-radius: 9999px;
    transition: all 200ms;
    outline: none;
    
    svg {
        width: 1.5rem;
        height: 1.5rem;
    }
`;

const BigPlayButton = styled.button`
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    outline: none;
    
    div {
        width: 5rem;
        height: 5rem;
        border-radius: 9999px;
        display: flex;
        align-items: center;
        justify-content: center;
        backdrop-filter: blur(4px);
        transition: all 300ms;
        transform: scale(0.9);
        opacity: 0;
    }

    svg {
        width: 2.5rem;
        height: 2.5rem;
        margin-left: 0.25rem;
    }

    .group:hover & div {
        opacity: 1;
        transform: scale(1);
    }
`;

const ProgressBarContainer = styled.div.attrs({ className: 'group bg-gray-600 hover:bg-gray-500' })`
    position: relative;
    width: 100%;
    height: 0.5rem;
    border-radius: 9999px;
    cursor: pointer;
    transition: height 150ms ease, background 150ms ease;
    
    &:hover {
        height: 0.625rem;
    }
`;

const ProgressBarFill = styled.div<{ width: number }>`
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    border-radius: 9999px;
    pointer-events: none;
    background: ${colors.primary.default};
    width: ${props => props.width}%;
`;

const ProgressBarThumb = styled.div.attrs({ className: 'bg-gray-300' }) <{ left: number }>`
    position: absolute;
    top: 50%;
    width: 0.875rem;
    height: 0.875rem;
    border-radius: 9999px;
    pointer-events: none;
    left: ${props => props.left}%;
    transform: translate(-50%, -50%) scale(0);
    transition: transform 150ms ease;
    
    .group:hover & {
        transform: translate(-50%, -50%) scale(1);
    }
`;

const TimeDisplay = styled.div.attrs({ className: 'text-white' })`
    font-size: 0.75rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    min-width: 80px;
    text-align: center;
`;

const VolumeContainer = styled.div`
    display: flex;
    align-items: center;
    gap: 0.5rem;
`;

const VolumeSlider = styled.input.attrs({ className: 'text-gray-500' })`
    width: 5rem;
    transition: background 200ms;
    height: 0.25rem;
    background: currentColor;
    border-radius: 0.5rem;
    appearance: none;
    cursor: pointer;
    &::-webkit-slider-thumb {
        appearance: none;
        width: 0.75rem;
        height: 0.75rem;
        background: currentColor;
        border-radius: 9999px;
        box-shadow: none;
    }
`;

const RateSlider = styled(VolumeSlider)`
    width: 6rem;
    transition: none;
`;

const AudioPlayerContainer = styled.div`
    width: 100%;
    height: 100%;
    flex: 1;
    display: flex;
    flex-direction: column;
    position: relative;
    overflow: hidden;
`;

const VisualizerCanvas = styled.canvas`
    width: 100%;
    height: 100%;
    position: absolute;
    inset: 0;
    opacity: 0.75;
`;

const AudioContent = styled.div`
    position: relative;
    z-index: 10;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    width: 100%;
    padding: 2rem;
    transform: scale(1);
    transition: transform 220ms ease-out;
    will-change: transform;

    @media (max-width: 640px) {
        padding: 1.25rem;
        justify-content: flex-start;
    }
`;

const AudioIconWrapper = styled.div<{ playing: boolean }>`
    width: 7rem;
    height: 7rem;
    border-radius: 9999px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 1.5rem;
    background: ${colors.primary.default};
    position: relative;
    z-index: 2;
    transition: transform 0.2s ease;
    
    ${props => props.playing && css`
        animation: pulse 2s ease-in-out infinite;
    `}

    @keyframes pulse {
        0%, 100% { transform: scale(1); }
        50% { transform: scale(1.02); }
    }

    @media (max-width: 640px) {
        width: 5.5rem;
        height: 5.5rem;
        margin-bottom: 0.75rem;
    }
`;

const AudioHeader = styled.div`
    display: flex;
    align-items: center;
    gap: 1.5rem;
    width: 100%;
    max-width: 48rem;
    justify-content: center;
    margin-bottom: 1.5rem;

    @media (max-width: 640px) {
        flex-direction: column;
        text-align: center;
        gap: 1rem;
        margin-bottom: 1rem;
    }
`;

const ArtworkStack = styled.div`
    position: relative;
    width: 7rem;
    height: 7rem;
    flex-shrink: 0;

    @media (max-width: 640px) {
        width: 5.5rem;
        height: 5.5rem;
    }
`;

const VinylDisk = styled.div<{ playing: boolean }>`
    position: absolute;
    width: 6.2rem;
    height: 6.2rem;
    top: 50%;
    left: -2.2rem;
    transform: translateY(-50%);
    border-radius: 9999px;
    background:
        radial-gradient(circle at center, rgba(255, 255, 255, 0.95) 0 7%, rgba(255, 255, 255, 0.2) 7% 10%, rgba(0, 0, 0, 0.95) 10% 55%, rgba(255, 255, 255, 0.22) 55% 58%, rgba(0, 0, 0, 0.98) 58% 100%),
        conic-gradient(from 110deg, rgba(255, 255, 255, 0.7) 0deg, rgba(255, 255, 255, 0.15) 22deg, rgba(0, 0, 0, 0.4) 60deg, rgba(255, 255, 255, 0.2) 120deg, rgba(0, 0, 0, 0.5) 170deg, rgba(255, 255, 255, 0.35) 230deg, rgba(0, 0, 0, 0.6) 320deg, rgba(255, 255, 255, 0.7) 360deg);
    border: 1px solid rgba(255, 255, 255, 0.35);
    box-shadow: 0 16px 30px rgba(0, 0, 0, 0.45);
    transform-origin: center;
    opacity: 0.9;
    z-index: 1;
    animation: ${props => (props.playing ? 'vinylSpin 3.6s linear infinite' : 'none')};

    @keyframes vinylSpin {
        from { transform: translateY(-50%) rotate(0deg); }
        to { transform: translateY(-50%) rotate(360deg); }
    }

    @media (max-width: 640px) {
        width: 4.8rem;
        height: 4.8rem;
        left: -1.6rem;
    }
`;

const BeatNotesLayer = styled.div`
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: visible;

    .beat-note {
        position: absolute;
        color: ${colors.primary.default};
        font-size: 1.2rem;
        opacity: 0.9;
        text-shadow: 0 6px 12px ${colors.primary.opacity(0.4)};
        transform: translate(0, 0) scale(0.85) rotate(0deg);
        animation: beatNoteFloat 1.1s ease-out forwards;
        will-change: transform, opacity;
    }

    @keyframes beatNoteFloat {
        0% {
            transform: translate(0, 0) scale(0.85) rotate(0deg);
            opacity: 0.95;
        }
        100% {
            transform: translate(var(--dx), var(--dy)) scale(1.25) rotate(18deg);
            opacity: 0;
        }
    }
`;

const AudioArtwork = styled.img`
    width: 7rem;
    height: 7rem;
    border-radius: 1rem;
    object-fit: cover;
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35);
    border: 1px solid ${colors.primary.opacity(0.35)};
    position: relative;
    z-index: 2;

    @media (max-width: 640px) {
        width: 5.5rem;
        height: 5.5rem;
        border-radius: 0.9rem;
    }
`;

const AudioMeta = styled.div`
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    min-width: 0;
`;

const AudioTitle = styled.div`
    font-size: 1.35rem;
    font-weight: 700;
    color: white;
    line-height: 1.2;
    max-width: 22rem;
    word-break: break-word;

    @media (max-width: 640px) {
        font-size: 1.1rem;
        max-width: 100%;
    }
`;

const AudioSubtitle = styled.div.attrs({ className: 'text-gray-400' })`
    font-size: 0.85rem;
    max-width: 22rem;
    word-break: break-word;

    @media (max-width: 640px) {
        max-width: 100%;
    }
`;

const AudioControls = styled(GreyRowBox)`
    display: block;
    width: 100%;
    max-width: 44rem;
    padding: 1.5rem;
    align-items: initial;
    justify-content: initial;
    backdrop-filter: blur(12px);
    background: ${themeColors.page.secondaryActive};
    border-color: ${themeColors.page.secondaryHover};

    @media (max-width: 640px) {
        padding: 1rem;
        max-width: 100%;
    }
`;

const WaveformScrubber = styled.div`
    position: relative;
    width: 100%;
    height: 2.2rem;
    border-radius: 0.9rem;
    overflow: hidden;
    cursor: pointer;
    background: ${themeColors.page.secondary};
    border: 1px solid ${themeColors.page.secondaryHover};
`;

const WaveformControls = styled.div`
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
`;

const ZoomControl = styled.div`
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.5rem;
    border-radius: 9999px;
    background: ${themeColors.page.secondary};
    border: 1px solid ${themeColors.page.secondaryHover};
`;

const ZoomButton = styled.button.attrs({ className: 'text-white bg-gray-700 hover:bg-gray-600' })`
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 9999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 150ms;
`;

const ZoomLabel = styled.span.attrs({ className: 'text-gray-400' })`
    font-size: 0.7rem;
`;

const WaveformCanvas = styled.canvas`
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
`;

const WaveformPlayhead = styled.div`
    position: absolute;
    top: 0.3rem;
    bottom: 0.3rem;
    width: 2px;
    background: ${colors.primary.default};
    box-shadow: 0 0 10px ${colors.primary.opacity(0.65)};
    border-radius: 9999px;
    pointer-events: none;
`;

const formatTime = (seconds: number) => {
    if (!Number.isFinite(seconds)) return '--:--';
    if (seconds <= 0) return '0:00';
    const totalSeconds = Math.floor(seconds);
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const secs = totalSeconds % 60;
    if (hours > 0) {
        return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    }
    return `${minutes}:${secs.toString().padStart(2, '0')}`;
};

interface ParsedAudioMetadata {
    title?: string;
    artist?: string;
    album?: string;
    picture?: { mime: string; data: Uint8Array };
}

const toArrayBuffer = (data: Uint8Array): ArrayBuffer => {
    if (data.buffer instanceof ArrayBuffer) {
        return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
    }
    const copy = new Uint8Array(data);
    return copy.buffer;
};

class BeatDetector {
    private prevSpectrum: Float32Array | null = null;
    private fluxHistory: number[] = [];
    private energyHistory: number[] = [];
    private prevFlux = 0;
    private prevFlux2 = 0;
    private beatIntervals: number[] = [];
    private lastBeatAt = 0;
    private lastDropAt = 0;
    private energyShort = 0.2;
    private energyLong = 0.25;

    update(spectrum: Float32Array, bassEnergy: number, totalEnergy: number, now: number) {
        if (!this.prevSpectrum || this.prevSpectrum.length !== spectrum.length) {
            this.prevSpectrum = new Float32Array(spectrum.length);
        }

        const len = spectrum.length;
        const lowEnd = Math.max(4, Math.floor(len * 0.12));
        const midEnd = Math.max(lowEnd + 2, Math.floor(len * 0.35));
        let fluxLow = 0;
        let fluxMid = 0;
        let fluxHigh = 0;

        for (let i = 0; i < len; i++) {
            const current = spectrum[i];
            const prev = this.prevSpectrum[i];
            const diff = current - prev;
            if (diff > 0) {
                if (i < lowEnd) fluxLow += diff;
                else if (i < midEnd) fluxMid += diff;
                else fluxHigh += diff;
            }
            this.prevSpectrum[i] = prev + (current - prev) * 0.65;
        }

        const totalFlux = (fluxLow * 1.25 + fluxMid * 0.9 + fluxHigh * 0.6) / (len * 255);
        this.fluxHistory.push(totalFlux);
        if (this.fluxHistory.length > 48) this.fluxHistory.shift();

        const fluxAvg = this.fluxHistory.reduce((sum, v) => sum + v, 0) / this.fluxHistory.length;
        const fluxStd = Math.sqrt(
            this.fluxHistory.reduce((sum, v) => sum + (v - fluxAvg) * (v - fluxAvg), 0) / this.fluxHistory.length
        );
        const fluxThreshold = fluxAvg + fluxStd * 1.1 + 0.008;

        this.energyShort = this.energyShort * 0.55 + totalEnergy * 0.45;
        this.energyLong = this.energyLong * 0.985 + totalEnergy * 0.015;

        const isFluxPeak = this.prevFlux > this.prevFlux2 && this.prevFlux > totalFlux && this.prevFlux > fluxThreshold;
        const isBeatEnergy = bassEnergy > 0.16 && (totalFlux > fluxThreshold * 0.9);
        const isBeat = (isFluxPeak || isBeatEnergy) && totalFlux > 0.01;

        const intervalAvg = this.beatIntervals.length
            ? this.beatIntervals.reduce((sum, v) => sum + v, 0) / this.beatIntervals.length
            : 420;
        const minInterval = Math.max(140, Math.min(420, intervalAvg * 0.55));

        let beat = false;
        let strength = 0;
        if (isBeat && now - this.lastBeatAt > minInterval) {
            beat = true;
            strength = Math.min(1.5, 0.6 + totalFlux * 6 + bassEnergy * 1.2);
            if (this.lastBeatAt > 0) {
                this.beatIntervals.push(now - this.lastBeatAt);
                if (this.beatIntervals.length > 12) this.beatIntervals.shift();
            }
            this.lastBeatAt = now;
        }

        const dropCandidate = this.energyShort < this.energyLong * 0.6 && bassEnergy < 0.14;
        const drop = dropCandidate && now - this.lastDropAt > 1200 && totalEnergy < 0.25;
        if (drop) this.lastDropAt = now;

        this.prevFlux2 = this.prevFlux;
        this.prevFlux = totalFlux;

        return { beat, strength, drop };
    }
}

const decodeTextFrame = (bytes: Uint8Array, encoding: number): string => {
    if (!bytes.length) return '';
    let decoder: TextDecoder;
    try {
        if (encoding === 0) decoder = new TextDecoder('iso-8859-1');
        else if (encoding === 1) decoder = new TextDecoder('utf-16');
        else if (encoding === 2) decoder = new TextDecoder('utf-16be');
        else decoder = new TextDecoder('utf-8');
    } catch {
        decoder = new TextDecoder('utf-8');
    }
    return decoder.decode(bytes).replace(/\0/g, '').trim();
};

const readSynchsafe = (bytes: Uint8Array, offset: number): number => {
    return ((bytes[offset] & 0x7f) << 21)
        | ((bytes[offset + 1] & 0x7f) << 14)
        | ((bytes[offset + 2] & 0x7f) << 7)
        | (bytes[offset + 3] & 0x7f);
};

const writeSynchsafe = (value: number): Uint8Array => {
    const safeValue = Math.max(0, value);
    return new Uint8Array([
        (safeValue >> 21) & 0x7f,
        (safeValue >> 14) & 0x7f,
        (safeValue >> 7) & 0x7f,
        safeValue & 0x7f,
    ]);
};

const readInt32 = (bytes: Uint8Array, offset: number): number => {
    return (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3];
};

const findTerminator = (bytes: Uint8Array, start: number, encoding: number): number => {
    if (encoding === 0 || encoding === 3) {
        for (let i = start; i < bytes.length; i += 1) {
            if (bytes[i] === 0x00) return i;
        }
        return bytes.length;
    }
    for (let i = start; i + 1 < bytes.length; i += 2) {
        if (bytes[i] === 0x00 && bytes[i + 1] === 0x00) return i;
    }
    return bytes.length;
};

const parseId3Metadata = (buffer: ArrayBuffer): ParsedAudioMetadata | null => {
    const bytes = new Uint8Array(buffer);
    if (bytes.length < 10) return null;
    if (bytes[0] !== 0x49 || bytes[1] !== 0x44 || bytes[2] !== 0x33) return null;

    const version = bytes[3];
    const tagSize = readSynchsafe(bytes, 6);
    const tagEnd = Math.min(bytes.length, 10 + tagSize);
    let offset = 10;

    const metadata: ParsedAudioMetadata = {};

    while (offset + 10 <= tagEnd) {
        const frameId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
        if (!frameId.trim()) break;
        const frameSize = version === 4 ? readSynchsafe(bytes, offset + 4) : readInt32(bytes, offset + 4);
        if (frameSize <= 0) break;
        const frameStart = offset + 10;
        const frameEnd = frameStart + frameSize;
        if (frameEnd > tagEnd) break;

        if (frameId === 'TIT2' || frameId === 'TPE1' || frameId === 'TALB') {
            const encoding = bytes[frameStart];
            const text = decodeTextFrame(bytes.slice(frameStart + 1, frameEnd), encoding);
            if (frameId === 'TIT2' && text) metadata.title = text;
            if (frameId === 'TPE1' && text) metadata.artist = text;
            if (frameId === 'TALB' && text) metadata.album = text;
        } else if (frameId === 'APIC') {
            const encoding = bytes[frameStart];
            let cursor = frameStart + 1;
            const mimeEnd = bytes.indexOf(0x00, cursor);
            const safeMimeEnd = mimeEnd === -1 ? frameEnd : mimeEnd;
            const mime = safeMimeEnd > cursor
                ? new TextDecoder('utf-8').decode(bytes.slice(cursor, safeMimeEnd))
                : 'image/jpeg';
            cursor = safeMimeEnd + 1;
            cursor += 1; 
            const descEnd = findTerminator(bytes, cursor, encoding);
            cursor = descEnd + (encoding === 0 || encoding === 3 ? 1 : 2);
            if (cursor < frameEnd) {
                metadata.picture = {
                    mime: mime || 'image/jpeg',
                    data: bytes.slice(cursor, frameEnd),
                };
            }
        }

        offset = frameEnd;
    }

    return metadata;
};

const concatBytes = (...chunks: Uint8Array[]): Uint8Array => {
    const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
    const output = new Uint8Array(total);
    let offset = 0;
    for (const chunk of chunks) {
        output.set(chunk, offset);
        offset += chunk.length;
    }
    return output;
};

const buildId3Frame = (id: string, payload: Uint8Array): Uint8Array => {
    const header = new Uint8Array(10);
    header[0] = id.charCodeAt(0);
    header[1] = id.charCodeAt(1);
    header[2] = id.charCodeAt(2);
    header[3] = id.charCodeAt(3);
    header.set(writeSynchsafe(payload.length), 4);
    header[8] = 0x00;
    header[9] = 0x00;
    return concatBytes(header, payload);
};

const buildTextFrame = (id: string, value: string): Uint8Array | null => {
    const trimmed = value.trim();
    if (!trimmed) return null;
    const encoder = new TextEncoder();
    const textBytes = encoder.encode(trimmed);
    const payload = new Uint8Array(1 + textBytes.length);
    payload[0] = 0x03;
    payload.set(textBytes, 1);
    return buildId3Frame(id, payload);
};

const buildApicFrame = (mime: string, imageBytes: Uint8Array): Uint8Array => {
    const encoder = new TextEncoder();
    const mimeBytes = encoder.encode(mime || 'image/jpeg');
    const payload = new Uint8Array(1 + mimeBytes.length + 1 + 1 + 1 + imageBytes.length);
    let offset = 0;
    payload[offset++] = 0x03;
    payload.set(mimeBytes, offset);
    offset += mimeBytes.length;
    payload[offset++] = 0x00;
    payload[offset++] = 0x03;
    payload[offset++] = 0x00;
    payload.set(imageBytes, offset);
    return buildId3Frame('APIC', payload);
};

const stripId3Tag = (bytes: Uint8Array): Uint8Array => {
    if (bytes.length < 10) return bytes;
    if (bytes[0] !== 0x49 || bytes[1] !== 0x44 || bytes[2] !== 0x33) return bytes;
    const tagSize = readSynchsafe(bytes, 6);
    const start = Math.min(bytes.length, 10 + tagSize);
    return bytes.slice(start);
};

const buildId3Tag = (frames: Uint8Array[]): Uint8Array => {
    const tagSize = frames.reduce((sum, frame) => sum + frame.length, 0);
    const header = new Uint8Array(10);
    header[0] = 0x49;
    header[1] = 0x44;
    header[2] = 0x33;
    header[3] = 0x04;
    header[4] = 0x00;
    header[5] = 0x00;
    header.set(writeSynchsafe(tagSize), 6);
    return concatBytes(header, ...frames);
};

const CustomVideoPlayer = ({ src, blobUrl, fileName, isStreaming, resumeState, onError, onWaiting, onPlaying, onTimeUpdate, onPlayingChange }: any) => {
    const videoRef = useRef<HTMLVideoElement>(null);
    const [playing, setPlaying] = useState(false);
    const [currentTime, setCurrentTime] = useState(0);
    const [duration, setDuration] = useState(0);
    const [seekPreview, setSeekPreview] = useState<number | null>(null);
    const [volume, setVolume] = useState(() => readStoredVolume());
    const [muted, setMuted] = useState(false);
    const [playbackRate, setPlaybackRate] = useState(1);
    const [fullscreen, setFullscreen] = useState(false);
    const [showControls, setShowControls] = useState(true);
    const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null);
    const pendingResumeRef = useRef<{ time: number; wasPlaying: boolean } | null>(null);
    const lastSrcRef = useRef<string | null>(null);
    const lastVolumeRef = useRef(readStoredVolume());
    const lastMutedRef = useRef(false);
    const lastRateRef = useRef(1);

    const applyMediaSettings = (video: HTMLVideoElement) => {
        video.volume = Math.min(1, Math.max(0, lastVolumeRef.current));
        video.muted = lastMutedRef.current;
        video.playbackRate = lastRateRef.current;
    };

    useEffect(() => {
        const video = videoRef.current;
        if (!video) return;

        const updateTime = () => setCurrentTime(video.currentTime);
        const updateDuration = () => {
            setDuration(video.duration);
            applyMediaSettings(video);
            if (pendingResumeRef.current) {
                const resume = pendingResumeRef.current;
                const target = Math.min(resume.time, Math.max(0, video.duration - 0.1));
                if (!Number.isNaN(target)) {
                    video.currentTime = target;
                }
                if (resume.wasPlaying) {
                    const promise = video.play();
                    if (promise && typeof promise.catch === 'function') {
                        promise.catch(() => { });
                    }
                }
                pendingResumeRef.current = null;
            }
        };
        const onPlay = () => { setPlaying(true); onPlaying?.(); onPlayingChange?.(true); };
        const onPause = () => { setPlaying(false); onPlayingChange?.(false); };
        const onVolumeChange = () => { setVolume(video.volume); setMuted(video.muted); };
        const onRateChange = () => { setPlaybackRate(video.playbackRate); };

        video.addEventListener('timeupdate', updateTime);
        video.addEventListener('loadedmetadata', updateDuration);
        video.addEventListener('play', onPlay);
        video.addEventListener('pause', onPause);
        video.addEventListener('volumechange', onVolumeChange);
        video.addEventListener('ratechange', onRateChange);
        video.addEventListener('waiting', onWaiting);
        video.addEventListener('canplay', onPlaying);
        return () => {
            video.removeEventListener('timeupdate', updateTime);
            video.removeEventListener('loadedmetadata', updateDuration);
            video.removeEventListener('play', onPlay);
            video.removeEventListener('pause', onPause);
            video.removeEventListener('volumechange', onVolumeChange);
            video.removeEventListener('ratechange', onRateChange);
            video.removeEventListener('waiting', onWaiting);
            video.removeEventListener('canplay', onPlaying);
        };
    }, [onWaiting, onPlaying, onPlayingChange]);

    useEffect(() => {
        const video = videoRef.current;
        if (!video || !src) return;
        const previousSrc = lastSrcRef.current;
        if (previousSrc && previousSrc !== src && isStreaming && resumeState) {
            pendingResumeRef.current = { time: resumeState.time, wasPlaying: resumeState.wasPlaying };
        }
        if (previousSrc && previousSrc !== src) {
            try {
                video.load();
            } catch {
            }
        }
        applyMediaSettings(video);
        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]);

    const togglePlay = () => {
        if (videoRef.current) {
            if (playing) videoRef.current.pause();
            else videoRef.current.play();
        }
    };

    const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {
        if (!videoRef.current) return;
        const rect = e.currentTarget.getBoundingClientRect();
        const percent = (e.clientX - rect.left) / rect.width;
        videoRef.current.currentTime = percent * duration;
    };

    const handleSeekPreview = (e: React.MouseEvent<HTMLDivElement>) => {
        if (!duration) return;
        const rect = e.currentTarget.getBoundingClientRect();
        const percent = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
        setSeekPreview(percent * duration);
    };

    const toggleFullscreen = () => {
        if (!document.fullscreenElement) {
            videoRef.current?.parentElement?.requestFullscreen();
            setFullscreen(true);
        } else {
            document.exitFullscreen();
            setFullscreen(false);
        }
    };

    const handleMouseMove = () => {
        setShowControls(true);
        if (controlsTimeoutRef.current) clearTimeout(controlsTimeoutRef.current);
        controlsTimeoutRef.current = setTimeout(() => {
            if (playing) setShowControls(false);
        }, 3000);
    };

    return (
        <PlayerContainer onMouseMove={handleMouseMove} onMouseLeave={() => playing && setShowControls(false)}>
            <VideoElement
                ref={videoRef}
                src={src}
                onClick={togglePlay}
                onDoubleClick={toggleFullscreen}
                onError={onError}
                preload="none"
            />

            <BigPlayButton onClick={togglePlay}>
                {!playing && (
                    <div className="bg-black border border-gray-500">
                        <svg className="text-white" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" d="M8 5l11 7-11 7V5z" />
                        </svg>
                    </div>
                )}
            </BigPlayButton>

            <ControlsOverlay visible={showControls || !playing}>
                <ControlsBar>
                    <ControlButton onClick={togglePlay}>
                        {playing ? (
                            <svg fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                                <path strokeLinecap="round" strokeLinejoin="round" d="M7 5v14M17 5v14" />
                            </svg>
                        ) : (
                            <svg fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                                <path strokeLinecap="round" strokeLinejoin="round" d="M8 5l11 7-11 7V5z" />
                            </svg>
                        )}
                    </ControlButton>

                    <VolumeContainer>
                        <ControlButton onClick={() => { if (videoRef.current) videoRef.current.muted = !muted; }}>
                            {muted || volume === 0 ? (
                                <svg fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" d="M9 9v6l-4 0v-6h4zm0 0l5-4v14l-5-4M19 9l-3 3m0-3l3 3" />
                                </svg>
                            ) : (
                                <svg fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" d="M9 9v6l-4 0v-6h4zm0 0l5-4v14l-5-4M16 8a4 4 0 010 8M18.5 6.5a7 7 0 010 11" />
                                </svg>
                            )}
                        </ControlButton>
                        <VolumeSlider
                            type="range" min="0" max="1" step="0.1"
                            value={muted ? 0 : volume}
                            onChange={(e) => { if (videoRef.current) { videoRef.current.volume = parseFloat(e.target.value); videoRef.current.muted = false; } }}
                        />
                    </VolumeContainer>

                    <TimeDisplay>{formatTime(currentTime)} / {formatTime(duration)}</TimeDisplay>

                    <Tooltip
                        placement="top"
                        content={seekPreview !== null ? formatTime(seekPreview) : formatTime(0)}
                        disabled={seekPreview === null || !duration}
                    >
                        <ProgressBarContainer
                            onClick={handleSeek}
                            onMouseMove={handleSeekPreview}
                            onMouseLeave={() => setSeekPreview(null)}
                        >
                            <ProgressBarFill width={(currentTime / duration) * 100 || 0} />
                            <ProgressBarThumb left={(currentTime / duration) * 100 || 0} />
                        </ProgressBarContainer>
                    </Tooltip>

                    {blobUrl && (
                        <a href={blobUrl} download={fileName} className="text-white p-2">
                            <svg className="w-6 h-6" fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                                <path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1M12 4v11m0 0l-4-4m4 4l4-4" />
                            </svg>
                        </a>
                    )}

                    <ControlButton onClick={toggleFullscreen}>
                        {fullscreen ? (
                            <svg fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                                <path strokeLinecap="round" strokeLinejoin="round" d="M9 9H5V5m14 4V5h-4M9 15H5v4m14-4v4h-4" />
                            </svg>
                        ) : (
                            <svg fill="none" stroke="currentColor" strokeWidth={2} viewBox="0 0 24 24">
                                <path strokeLinecap="round" strokeLinejoin="round" d="M5 9V5h4M15 5h4v4M9 19H5v-4M19 15v4h-4" />
                            </svg>
                        )}
                    </ControlButton>
                </ControlsBar>
            </ControlsOverlay>
        </PlayerContainer>
    );
};

const CustomAudioPlayer = ({
    src,
    blobUrl,
    blob,
    blobPartial,
    isStreaming,
    resumeState,
    fileName,
    filePath,
    uuid,
    onError,
    onTimeUpdate,
    onPlayingChange,
    onRequestFullDownload,
    editMetadataRequest,
}: any) => {
    const audioRef = useRef<HTMLAudioElement>(null);
    const canvasRef = useRef<HTMLCanvasElement>(null);
    const timelineCanvasRef = useRef<HTMLCanvasElement>(null);
    const iconRef = useRef<HTMLElement | null>(null);
    const playerIdRef = useRef<string>(`audio-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
    const bassPulseRef = useRef(0);
    const bassFastRef = useRef(0);
    const bassSlowRef = useRef(0);
    const prevBassRef = useRef(0);
    const zoomOutRef = useRef(0);
    const zoomRef = useRef(1);
    const particlesRef = useRef<Array<{ x: number; y: number; vx: number; vy: number; size: number; baseSize: number; alpha: number; phase: number }>>([]);
    const rotationRef = useRef(0);
    const rotationTargetRef = useRef(0);
    const rotationVelocityRef = useRef(0);
    const lastBeatTimeRef = useRef(0);
    const lastNoteTimeRef = useRef(0);
    const amplitudeHistoryRef = useRef<Float32Array | null>(null);
    const timeHistoryRef = useRef<Uint8Array[]>([]);
    const beatZoomRef = useRef(1);
    const amplitudeWeightRef = useRef<Float32Array | null>(null);
    const beatDetectorRef = useRef<BeatDetector | null>(null);
    const audioContentRef = useRef<HTMLDivElement>(null);
    const [playing, setPlaying] = useState(false);
    const [currentTime, setCurrentTime] = useState(0);
    const [duration, setDuration] = useState(0);
    const [volume, setVolume] = useState(() => readStoredVolume());
    const [muted, setMuted] = useState(false);
    const [playbackRate, setPlaybackRate] = useState(1);
    const peakLevelRef = useRef(0.5);
    const avgLevelRef = useRef(0.3);
    const [syncCount, setSyncCount] = useState(0);
    const onPlayingChangeRef = useRef<typeof onPlayingChange>();
    const [metadata, setMetadata] = useState<ParsedAudioMetadata | null>(null);
    const [artworkUrl, setArtworkUrl] = useState<string | null>(null);
    const artworkUrlRef = useRef<string | null>(null);
    const animationRef = useRef<number>();
    const analyserRef = useRef<AnalyserNode>();
    const smoothedBarsRef = useRef<Float32Array | 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 MAX_METADATA_EDIT_SIZE = 25 * 1024 * 1024;
    const isMp3 = (fileName || '').toLowerCase().endsWith('.mp3');
    const canEditMetadata = !!blob && !blobPartial && isMp3 && blob.size <= MAX_METADATA_EDIT_SIZE;
    const editHint = !canEditMetadata
        ? (!isMp3 ? 'MP3 only' : isStreaming ? 'Streaming mode' : !blob ? 'File not ready' : blobPartial ? 'Streaming mode' : 'Max 25 MB')
        : '';
    const [showEditModal, setShowEditModal] = useState(false);
    const [editTitle, setEditTitle] = useState('');
    const [editArtist, setEditArtist] = useState('');
    const [editAlbum, setEditAlbum] = 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 [editSaving, setEditSaving] = useState(false);
    const [editProgress, setEditProgress] = useState(0);
    const [seekPreview, setSeekPreview] = useState<number | null>(null);
    const lastVolumeRef = useRef(readStoredVolume());
    const lastMutedRef = useRef(false);
    const lastRateRef = useRef(1);
    const notesLayerRef = useRef<HTMLDivElement>(null);
    const isScrubbingRef = useRef(false);
    const scrubRafRef = useRef<number | null>(null);

    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();
            if (parsed?.picture?.data && parsed.picture.data.length > 0) {
                const imageBlob = new Blob([toArrayBuffer(parsed.picture.data)], { type: parsed.picture.mime || 'image/jpeg' });
                const url = URL.createObjectURL(imageBlob);
                artworkUrlRef.current = url;
                setArtworkUrl(url);
            } else {
                setArtworkUrl(null);
            }
        };

        const loadFromBlob = async () => {
            const maxBytes = 512 * 1024;
            const slice = blob!.slice(0, Math.min(maxBytes, blob!.size));
            const buffer = await slice.arrayBuffer();
            const parsed = parseId3Metadata(buffer);
            if (!active) return;
            setMetadata(parsed);
            setParsedArtwork(parsed);
        };

        const loadFromStream = async () => {
            try {
                const res = await fetch(src, {
                    headers: { Range: 'bytes=0-524287' },
                    signal: controller.signal,
                });
                if (!res.ok) throw new Error('stream metadata fetch failed');
                const buffer = await res.arrayBuffer();
                const parsed = parseId3Metadata(buffer);
                if (!active) return;
                setMetadata(parsed);
                setParsedArtwork(parsed);
            } catch {
                if (!active) return;
                setMetadata(null);
                resetArtwork();
                setArtworkUrl(null);
            }
        };

        const loadMetadata = async () => {
            if (blob && !blobPartial) {
                await loadFromBlob();
                return;
            }
            if (isStreaming && src) {
                await loadFromStream();
                return;
            }
            setMetadata(null);
            resetArtwork();
            setArtworkUrl(null);
        };

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

    const applyMediaSettings = (audio: HTMLAudioElement) => {
        audio.volume = Math.min(1, Math.max(0, lastVolumeRef.current));
        audio.muted = lastMutedRef.current;
        audio.playbackRate = lastRateRef.current;
        (audio as any).preservesPitch = false;
        (audio as any).mozPreservesPitch = false;
        (audio as any).webkitPreservesPitch = false;
    };

    const emitBeatNote = (strength: number) => {
        const layer = notesLayerRef.current;
        const iconEl = iconRef.current;
        if (!layer || !iconEl) return;

        const layerRect = layer.getBoundingClientRect();
        const iconRect = iconEl.getBoundingClientRect();
        const centerX = iconRect.left + iconRect.width / 2 - layerRect.left;
        const centerY = iconRect.top + iconRect.height / 2 - layerRect.top;

        const note = document.createElement('span');
        note.className = 'beat-note';
        note.textContent = Math.random() > 0.5 ? '♪' : '♫';

        const direction = Math.random() > 0.5 ? 1 : -1;
        const angle = (Math.random() * 0.6 + 0.2) * direction;
        const distance = 38 + strength * 90;
        const dx = Math.cos(angle) * distance;
        const dy = -Math.sin(angle) * (48 + strength * 80);

        note.style.left = `${centerX + direction * (iconRect.width * 0.15)}px`;
        note.style.top = `${centerY - iconRect.height * 0.1}px`;
        note.style.setProperty('--dx', `${dx}px`);
        note.style.setProperty('--dy', `${dy}px`);
        note.style.animationDuration = `${0.85 + Math.random() * 0.5}s`;

        layer.appendChild(note);
        note.addEventListener('animationend', () => {
            note.remove();
        }, { once: true });
    };

    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.8;
            analyser.minDecibels = -90;
            analyser.maxDecibels = -10;

            try {
                const source = audioCtx.createMediaElementSource(audio);
                source.connect(analyser);
                analyser.connect(audioCtx.destination);

                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);
            applyMediaSettings(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 onPlay = () => { setPlaying(true); drawVisualizer(); onPlayingChangeRef.current?.(true); };
        const onPause = () => { setPlaying(false); if (animationRef.current) cancelAnimationFrame(animationRef.current); onPlayingChangeRef.current?.(false); };
        const onVolumeChange = () => { setVolume(audio.volume); setMuted(audio.muted); };
        const onRateChange = () => { setPlaybackRate(audio.playbackRate); };

        audio.addEventListener('timeupdate', updateTime);
        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);
            drawVisualizer();
        }

        return () => {
            audio.removeEventListener('timeupdate', updateTime);
            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);
        };
    }, [src]);

    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) {
            try {
                audio.load();
            } catch {
            }
        }
        applyMediaSettings(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) {
            drawVisualizer();
        }
    }, [playing]);

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

        const canvas = canvasRef.current;
        const ctx = canvas.getContext('2d');
        if (!ctx) return;

        const { clientWidth, clientHeight } = canvas;
        const dpr = window.devicePixelRatio || 1;
        const targetWidth = Math.max(1, Math.floor(clientWidth * dpr));
        const targetHeight = Math.max(1, Math.floor(clientHeight * dpr));
        if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
            canvas.width = targetWidth;
            canvas.height = targetHeight;
        }
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        ctx.imageSmoothingEnabled = true;

        const bufferLength = analyserRef.current.frequencyBinCount;
        const dataArray = new Uint8Array(bufferLength);
        const dbArray = new Float32Array(bufferLength);

        const ensureParticles = () => {
            if (particlesRef.current.length > 0) return;
            const count = 45;
            const particles = Array.from({ length: count }).map((_, idx) => ({
                x: Math.random() * clientWidth,
                y: Math.random() * clientHeight,
                vx: (Math.random() < 0.5 ? -1 : 1) * (0.3 + Math.random() * 0.8),
                vy: (Math.random() < 0.5 ? -1 : 1) * (0.3 + Math.random() * 0.8),
                size: 1.5 + Math.random() * 3,
                baseSize: 1.5 + Math.random() * 3,
                alpha: 0.2 + Math.random() * 0.4,
                phase: idx * 0.23,
            }));
            particlesRef.current = particles;
        };

        const draw = () => {
            animationRef.current = requestAnimationFrame(draw);
            analyserRef.current!.getByteFrequencyData(dataArray);


            if (!smoothedBarsRef.current || smoothedBarsRef.current.length !== dataArray.length) {
                smoothedBarsRef.current = new Float32Array(dataArray.length);
                smoothedBarsRef.current.set(dataArray);
            } else {
                const smoothUp = 0.28;
                const smoothDown = 0.08;
                for (let i = 0; i < dataArray.length; i++) {
                    const current = smoothedBarsRef.current[i];
                    const target = dataArray[i];
                    const rate = target > current ? smoothUp : smoothDown;
                    smoothedBarsRef.current[i] = current + (target - current) * rate;
                }
            }
            analyserRef.current!.getFloatFrequencyData(dbArray);


            const bassBins = Math.max(8, Math.floor(bufferLength * 0.12));
            const midBins = Math.floor(bufferLength * 0.35);
            let bassDbSum = 0, midDbSum = 0, highDbSum = 0;
            for (let i = 0; i < bassBins; i++) bassDbSum += dbArray[i];
            for (let i = bassBins; i < midBins; i++) midDbSum += dbArray[i];
            for (let i = midBins; i < bufferLength; i++) highDbSum += dbArray[i];

            const minDb = analyserRef.current!.minDecibels;
            const maxDb = analyserRef.current!.maxDecibels;
            const normalize = (sum: number, count: number) => {
                const avg = sum / count;
                return Math.max(0, Math.min(1, (avg - minDb) / (maxDb - minDb)));
            };

            const bassEnergy = Math.pow(normalize(bassDbSum, bassBins), 1.1);
            const midEnergy = Math.pow(normalize(midDbSum, midBins - bassBins), 1.2);
            const highEnergy = Math.pow(normalize(highDbSum, bufferLength - midBins), 1.3);
            const totalEnergy = bassEnergy * 0.5 + midEnergy * 0.3 + highEnergy * 0.2;

            const bassDrop = Math.max(0, prevBassRef.current - bassEnergy);
            prevBassRef.current = bassEnergy;

            bassFastRef.current = bassFastRef.current * 0.4 + bassEnergy * 0.6;
            bassSlowRef.current = bassSlowRef.current * 0.85 + bassEnergy * 0.15;
            const beatEnergy = Math.max(0, bassFastRef.current - bassSlowRef.current);
            bassPulseRef.current = bassPulseRef.current * 0.55 + bassEnergy * 0.45;

            if (beatEnergy > 0.06 || bassDrop > 0.05) {
                zoomOutRef.current = Math.min(0.28, zoomOutRef.current + (beatEnergy + bassDrop) * 0.6);
            } else {
                zoomOutRef.current *= 0.82;
            }
            const zoomTarget = 1
                + bassPulseRef.current * 0.2
                + beatEnergy * 0.18
                - zoomOutRef.current * 0.9;
            zoomRef.current += (zoomTarget - zoomRef.current) * 0.12;
            const zoomScale = Math.max(0.88, Math.min(1.22, zoomRef.current));

            const now = performance.now();
            if (!beatDetectorRef.current) {
                beatDetectorRef.current = new BeatDetector();
            }
            const detector = beatDetectorRef.current;
            const spectrum = (smoothedBarsRef.current ?? dataArray) as Float32Array;
            const { beat, strength, drop } = detector.update(spectrum, bassEnergy, totalEnergy, now);

            if (beat) {
                const direction = Math.random() < 0.5 ? -1 : 1;
                rotationTargetRef.current = direction * Math.min(6, 1.1 + strength * 4);
                lastBeatTimeRef.current = now;
                beatZoomRef.current = Math.min(1.6, beatZoomRef.current + 0.28 + strength * 0.25);
                if (now - lastNoteTimeRef.current > 160) {
                    emitBeatNote(strength);
                    lastNoteTimeRef.current = now;
                }
            }
            if (drop) {
                beatZoomRef.current = Math.min(1.9, beatZoomRef.current + 0.6);
            }
            beatZoomRef.current += (1 - beatZoomRef.current) * 0.12;
            if (audioContentRef.current) {
                const scale = 1 + (beatZoomRef.current - 1) * 0.25;
                audioContentRef.current.style.transform = `scale(${scale})`;
            }
            const slowWave = Math.sin(now * 0.0008) * 0.4;
            rotationTargetRef.current += slowWave * 0.08;
            rotationTargetRef.current *= bassEnergy < 0.15 ? 0.85 : 0.92;
            rotationRef.current += (rotationTargetRef.current - rotationRef.current) * 0.09;
            const rotationRad = (rotationRef.current * Math.PI) / 180;

            ctx.clearRect(0, 0, clientWidth, clientHeight);

            ensureParticles();
            if (particlesRef.current.length) {
                const drift = 0.35 + bassEnergy * 0.9;
                const pulse = 0.12 + beatEnergy * 0.35;
                ctx.save();
                ctx.globalCompositeOperation = 'lighter';
                for (const p of particlesRef.current) {
                    p.x += p.vx * drift;
                    p.y += p.vy * drift;
                    if (p.x < -10) p.x = clientWidth + 10;
                    if (p.x > clientWidth + 10) p.x = -10;
                    if (p.y < -10) p.y = clientHeight + 10;
                    if (p.y > clientHeight + 10) p.y = -10;

                    const size = p.baseSize * (1 + pulse);
                    ctx.fillStyle = colors.primary.opacity(0.08 + p.alpha * 0.2 + pulse * 0.2);
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, size, 0, Math.PI * 2);
                    ctx.fill();
                }
                ctx.restore();
            }

            ctx.save();
            ctx.translate(clientWidth / 2, clientHeight / 2);
            ctx.rotate(rotationRad);
            ctx.scale(zoomScale, zoomScale);
            ctx.translate(-clientWidth / 2, -clientHeight / 2);
            const intensity = 1 + bassPulseRef.current * 1.8 + beatEnergy * 0.8;
            const sampleBin = (index: number, count: number) => {
                const start = Math.floor((index / count) * bufferLength);
                const end = Math.min(bufferLength - 1, Math.floor(((index + 1) / count) * bufferLength) - 1);
                if (end < start) return smoothedBarsRef.current?.[start] ?? dataArray[start];
                let sum = 0;
                const arr = smoothedBarsRef.current ?? dataArray;
                for (let i = start; i <= end; i++) sum += arr[i];
                return sum / (end - start + 1);
            };
            const timeData = new Uint8Array(bufferLength);
            analyserRef.current!.getByteTimeDomainData(timeData);
            const sampleTime = (data: Uint8Array, index: number, count: number) => {
                const start = Math.floor((index / count) * data.length);
                const end = Math.min(data.length - 1, Math.floor(((index + 1) / count) * data.length) - 1);
                if (end < start) return 0;
                let sumSq = 0;
                for (let i = start; i <= end; i++) {
                    const v = (data[i] - 128) / 128;
                    sumSq += v * v;
                }
                return Math.sqrt(sumSq / (end - start + 1));
            };

                timeHistoryRef.current.push(new Uint8Array(timeData));
                if (timeHistoryRef.current.length > 10) {
                    timeHistoryRef.current.shift();
                }

            const drawTimeline = () => {
                const timelineCanvas = timelineCanvasRef.current;
                if (!timelineCanvas) return;
                const tctx = timelineCanvas.getContext('2d');
                if (!tctx) return;

                const tWidth = Math.max(1, timelineCanvas.clientWidth);
                const tHeight = Math.max(1, timelineCanvas.clientHeight);
                const tDpr = window.devicePixelRatio || 1;
                const tTargetWidth = Math.floor(tWidth * tDpr);
                const tTargetHeight = Math.floor(tHeight * tDpr);
                if (timelineCanvas.width !== tTargetWidth || timelineCanvas.height !== tTargetHeight) {
                    timelineCanvas.width = tTargetWidth;
                    timelineCanvas.height = tTargetHeight;
                }
                tctx.setTransform(tDpr, 0, 0, tDpr, 0, 0);
                tctx.clearRect(0, 0, tWidth, tHeight);

                const baseline = tHeight * 0.5;
                const amplitude = tHeight * 0.28;
                const rawDuration = audioRef.current?.duration;
                const timelineDuration = Number.isFinite(rawDuration) && (rawDuration as number) > 0
                    ? (rawDuration as number)
                    : duration;
                const progress = timelineDuration
                    ? Math.min(1, (audioRef.current?.currentTime || 0) / timelineDuration)
                    : 0;
                const progressX = progress * tWidth;

                const history = timeHistoryRef.current;
                const historyCount = Math.max(1, history.length);
                const points = Math.max(120, Math.floor(tWidth / 2));
                const path = new Path2D();
                for (let i = 0; i < points; i++) {
                    const idx = Math.floor((i / (points - 1)) * (bufferLength - 1));
                    let sum = 0;
                    for (const snapshot of history) {
                        sum += snapshot[idx] ?? 128;
                    }
                    const avg = sum / historyCount;
                    const value = (avg - 128) / 128;
                    const x = (i / (points - 1)) * tWidth;
                    const y = baseline + value * amplitude;
                    if (i === 0) path.moveTo(x, y);
                    else path.lineTo(x, y);
                }

                const zoom = beatZoomRef.current;
                tctx.strokeStyle = colors.primary.opacity(0.24);
                tctx.lineWidth = 2.6 * zoom;
                tctx.lineJoin = 'round';
                tctx.lineCap = 'round';
                tctx.stroke(path);

                tctx.save();
                tctx.beginPath();
                tctx.rect(0, 0, progressX, tHeight);
                tctx.clip();
                tctx.strokeStyle = colors.primary.default;
                tctx.lineWidth = 2.9 * zoom;
                tctx.stroke(path);
                tctx.restore();

                tctx.fillStyle = colors.primary.opacity(0.55);
                tctx.fillRect(progressX - 0.75, tHeight * 0.12, 1.5, tHeight * 0.76);
            };

            const barCount = Math.max(48, Math.min(128, Math.floor(clientWidth / 12)));
            const gap = 3;
            const totalGaps = (barCount - 1) * gap;
            const barWidth = Math.max(4, (clientWidth - totalGaps) / barCount);
            const maxBarHeight = clientHeight * 0.85;
            const borderRadius = Math.max(2, barWidth / 2.5);

                if (!amplitudeHistoryRef.current || amplitudeHistoryRef.current.length !== barCount) {
                    amplitudeHistoryRef.current = new Float32Array(barCount);
                }
                if (!amplitudeWeightRef.current || amplitudeWeightRef.current.length !== barCount) {
                    amplitudeWeightRef.current = new Float32Array(barCount);
                    for (let i = 0; i < barCount; i++) {
                        amplitudeWeightRef.current[i] = 0.9 + (Math.sin(i * 12.9898) * 0.5 + 0.5) * 0.2;
                    }
                }

                let frameMax = 0;
                let frameSum = 0;
                const rawValues: number[] = [];

                for (let i = 0; i < barCount; i++) {
                    const normalized = sampleBin(i, barCount) / 255;
                    rawValues.push(normalized);
                    frameMax = Math.max(frameMax, normalized);
                    frameSum += normalized;
                }
                const frameAvg = frameSum / barCount;

                peakLevelRef.current = peakLevelRef.current * 0.95 + frameMax * 0.05;
                avgLevelRef.current = avgLevelRef.current * 0.98 + frameAvg * 0.02;

                const targetLevel = 0.65;
                const currentPeak = Math.max(0.1, peakLevelRef.current);
                const normalizeScale = Math.min(1.8, targetLevel / currentPeak);

                const gradient = ctx.createLinearGradient(0, clientHeight, 0, 0);
                gradient.addColorStop(0, colors.primary.dark);
                gradient.addColorStop(0.3, colors.primary.default);
                gradient.addColorStop(0.7, colors.primary.light);
                gradient.addColorStop(1, colors.primary.lighter);

                for (let i = 0; i < barCount; i++) {
                    const raw = rawValues[i];
                    const history = timeHistoryRef.current;
                    const delayIndex = history.length > 1
                        ? Math.floor((i / (barCount - 1)) * (history.length - 1))
                        : 0;
                    const snapshot = history[delayIndex] || timeData;
                    const timeRaw = sampleTime(snapshot, i, barCount);
                    const blended = raw * 0.6 + timeRaw * 0.4;

                    const prev = amplitudeHistoryRef.current[i];
                    const smoothUp = 0.18;
                    const smoothDown = 0.08;
                    const rate = blended > prev ? smoothUp : smoothDown;
                    const smoothed = prev + (blended - prev) * rate;
                    amplitudeHistoryRef.current[i] = smoothed;

                    const weight = amplitudeWeightRef.current[i];
                    const normalizedValue = Math.min(1, smoothed * normalizeScale);
                    const compressed = Math.pow(normalizedValue, 0.85);
                    const beatScale = 1 + Math.min(0.2, Math.max(0, beatZoomRef.current - 1) * 0.5);
                    const value = compressed * intensity * weight * beatScale;
                    const barHeight = Math.max(4, Math.min(maxBarHeight, value * maxBarHeight));
                    const x = i * (barWidth + gap);
                    const y = clientHeight - barHeight;

                    ctx.fillStyle = gradient;
                    ctx.beginPath();
                    drawRoundRect(ctx, x, y, barWidth, barHeight, [borderRadius, borderRadius, 0, 0]);
                    ctx.fill();

                    if (barHeight > maxBarHeight * 0.6) {
                        ctx.save();
                        ctx.globalAlpha = 0.3 * (barHeight / maxBarHeight);
                        ctx.shadowColor = colors.primary.default;
                        ctx.shadowBlur = 8;
                        ctx.fillStyle = colors.primary.light;
                        ctx.beginPath();
                        drawRoundRect(ctx, x, y, barWidth, barHeight, [borderRadius, borderRadius, 0, 0]);
                        ctx.fill();
                        ctx.restore();
                    }
                }
            ctx.restore();
            drawTimeline();
        };
        draw();
    };

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

    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 = (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);
    };

    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 = (nextVolume: number) => {
        if (!audioRef.current) return;
        audioRef.current.volume = nextVolume;
        audioRef.current.muted = nextVolume === 0;
    };

    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 = () => {
        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?.();
        }
    };

    useEffect(() => {
        if (!editMetadataRequest) return;
        openEditModal();
    }, [editMetadataRequest]);

    const closeEditModal = () => {
        resetEditPreview();
        setEditCoverFile(null);
        setEditCoverPreview(null);
        setEditError(null);
        setShowEditModal(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 handleSaveMetadata = async () => {
        if (!blob || !filePath || !uuid) return;
        if (!canEditMetadata) {
            setEditError('Metadata edits are only available for MP3 files under 25 MB.');
            return;
        }
        setEditSaving(true);
        setEditError(null);
        setEditProgress(0);
        try {
            const audioBuffer = await blob.arrayBuffer();
            const audioBytes = stripId3Tag(new Uint8Array(audioBuffer));

            const frames: Uint8Array[] = [];
            const titleFrame = buildTextFrame('TIT2', editTitle);
            const artistFrame = buildTextFrame('TPE1', editArtist);
            const albumFrame = buildTextFrame('TALB', editAlbum);
            if (titleFrame) frames.push(titleFrame);
            if (artistFrame) frames.push(artistFrame);
            if (albumFrame) frames.push(albumFrame);

            let picture: ParsedAudioMetadata['picture'] | null = null;
            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;
            }

            if (picture?.data?.length) {
                frames.push(buildApicFrame(picture.mime, picture.data));
            }

            let outputBytes = audioBytes;
            if (frames.length > 0) {
                const tagBytes = buildId3Tag(frames);
                outputBytes = concatBytes(tagBytes, audioBytes);
            }

            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) {
                        setEditProgress((event.loaded / event.total) * 100);
                    }
                },
            });

            const nextMetadata: ParsedAudioMetadata = {
                title: editTitle.trim() || undefined,
                artist: editArtist.trim() || undefined,
                album: editAlbum.trim() || undefined,
                picture: picture || undefined,
            };
            setMetadata(nextMetadata);
            if (artworkUrlRef.current) {
                URL.revokeObjectURL(artworkUrlRef.current);
                artworkUrlRef.current = null;
            }
            if (picture?.data?.length) {
                const imageBlob = new Blob([toArrayBuffer(picture.data)], { type: picture.mime || 'image/jpeg' });
                const url = URL.createObjectURL(imageBlob);
                artworkUrlRef.current = url;
                setArtworkUrl(url);
            } else {
                setArtworkUrl(null);
            }

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

    return (
        <AudioPlayerContainer>
            <VisualizerCanvas ref={canvasRef} width={800} height={400} />
            <audio ref={audioRef} src={src} onError={onError} preload="metadata" crossOrigin="anonymous" />

            <AudioContent ref={audioContentRef}>
                <BeatNotesLayer ref={notesLayerRef} />
                <AudioHeader>
                    <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>

                    <AudioMeta>
                        <AudioTitle>{metadata?.title || fileName?.replace(/\.[^/.]+$/, '') || 'Audio file'}</AudioTitle>
                        {(metadata?.artist || metadata?.album) && (
                            <>
                                {metadata?.artist && <AudioSubtitle>{metadata.artist}</AudioSubtitle>}
                                {metadata?.album && <AudioSubtitle>{metadata.album}</AudioSubtitle>}
                            </>
                        )}
                        <div className="flex items-center gap-2 mt-3 flex-wrap">
                            <button
                                type="button"
                                onClick={openEditModal}
                                disabled={!canEditMetadata || editSaving}
                                className={cx(
                                    'text-xs font-semibold px-3 py-1.5 transition-colors',
                                    canEditMetadata
                                        ? 'text-white bg-gray-600 hover:bg-gray-500'
                                        : 'text-gray-500 bg-gray-700 cursor-not-allowed'
                                )}
                                style={{ borderRadius: themeColors.borderRadius.component }}
                            >
                                Edit metadata
                            </button>
                            {!canEditMetadata && (
                                <span className="text-xs text-yellow-400">{editHint}</span>
                            )}
                        </div>
                    </AudioMeta>
                </AudioHeader>

                <AudioControls>
                    <div className="grid grid-cols-[minmax(10rem,14rem)_minmax(20rem,1fr)_minmax(10rem,14rem)] items-center gap-4 mb-5">
                        <div className="flex flex-col gap-2">
                            <div className="text-xs text-neutral-300">Change volume</div>
                            <VolumeContainer>
                                <ControlButton onClick={() => { if (audioRef.current) audioRef.current.muted = !muted; }}>
                                    <svg
                                        fill="none"
                                        stroke="currentColor"
                                        strokeWidth={2}
                                        viewBox="0 0 24 24"
                                        style={{ opacity: muted || volume === 0 ? 0.5 : 1 }}
                                    >
                                        <path strokeLinecap="round" strokeLinejoin="round" d="M9 9v6l-4 0v-6h4zm0 0l5-4v14l-5-4M16 8a4 4 0 010 8M18.5 6.5a7 7 0 010 11" />
                                    </svg>
                                </ControlButton>
                                <VolumeSlider
                                    type="range"
                                    min="0"
                                    max="1"
                                    step="0.05"
                                    value={muted ? 0 : volume}
                                    onChange={(e) => handleVolumeChange(parseFloat(e.target.value))}
                                />
                            </VolumeContainer>
                        </div>

                        <div className="flex flex-col gap-2">
                            {(() => {
                                const safeAudioDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
                                return (
                                    <>
                                        <Tooltip
                                            placement="top"
                                            content={seekPreview !== null ? formatTime(seekPreview) : formatTime(0)}
                                            disabled={seekPreview === null || !safeAudioDuration}
                                        >
                                            <WaveformScrubber
                                                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);
                                                }}
                                            >
                                                <WaveformCanvas ref={timelineCanvasRef} />
                                                <WaveformPlayhead style={{ left: `${safeAudioDuration ? (currentTime / safeAudioDuration) * 100 : 0}%` }} />
                                            </WaveformScrubber>
                                        </Tooltip>
                                        <div className="flex items-center justify-between text-xs text-neutral-300 px-1">
                                            <span className="tabular-nums min-w-[3.5rem] text-left">{formatTime(currentTime)}</span>
                                            <span className="tabular-nums min-w-[3.5rem] text-right">{formatTime(duration)}</span>
                                        </div>
                                    </>
                                );
                            })()}
                        </div>

                        <div className="flex flex-col items-end gap-2">
                            <div className="text-xs text-neutral-300">Slow down</div>
                            <div className="flex items-center gap-2">
                                <span className="text-xs text-white tabular-nums">{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>
                        </div>
                    </div>

                    <div className="flex items-center justify-center gap-8">
                        <ControlButton onClick={() => skipTime(-10)}>
                            <svg 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>
                        </ControlButton>

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

                        <ControlButton onClick={() => skipTime(10)}>
                            <svg 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>
                        </ControlButton>
                    </div>

                    {syncCount > 1 && (
                        <div className="flex items-center justify-center gap-3 mt-4 pt-4 border-t border-gray-600">
                            <button
                                onClick={() => syncSkipAll(-10)}
                                className="flex items-center gap-1 px-3 py-2 text-sm font-medium bg-gray-600 text-white hover:bg-gray-500 transition-all"
                                style={{ borderRadius: themeColors.borderRadius.component }}
                                title="Skip all back 10s"
                            >
                                <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12.066 11.2a1 1 0 000 1.6l5.334 4A1 1 0 0019 16V8a1 1 0 00-1.6-.8l-5.333 4zM4.066 11.2a1 1 0 000 1.6l5.334 4A1 1 0 0011 16V8a1 1 0 00-1.6-.8l-5.334 4z" />
                                </svg>
                                -10s
                            </button>
                            <button
                                onClick={() => syncSeekAll(0)}
                                className="flex items-center gap-1 px-3 py-2 text-sm font-medium bg-gray-600 text-white hover:bg-gray-500 transition-all"
                                style={{ borderRadius: themeColors.borderRadius.component }}
                                title="Reset all to start"
                            >
                                <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
                                    <path d="M6 6h2v12H6zm3.5 6l8.5 6V6z" />
                                </svg>
                            </button>
                            <button
                                onClick={() => syncAllAudio('toggle')}
                                className="text-white flex items-center gap-2 px-4 py-2 text-sm font-medium transition-all hover:scale-105"
                                style={{ backgroundColor: colors.primary.default, borderRadius: themeColors.borderRadius.component }}
                                title="Play/Pause all"
                            >
                                <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
                                </svg>
                                Sync ({syncCount})
                            </button>
                            <button
                                onClick={() => syncSkipAll(10)}
                                className="flex items-center gap-1 px-3 py-2 text-sm font-medium bg-gray-600 text-white hover:bg-gray-500 transition-all"
                                style={{ borderRadius: themeColors.borderRadius.component }}
                                title="Skip all forward 10s"
                            >
                                +10s
                                <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
                                </svg>
                            </button>
                        </div>
                    )}
                </AudioControls>

                <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>
                        {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
                                            type="button"
                                            className="px-3 py-2 text-xs font-semibold text-white bg-gray-600 hover:bg-gray-500"
                                            style={{ borderRadius: themeColors.borderRadius.component }}
                                            onClick={() => coverInputRef.current?.click()}
                                        >
                                            Choose cover
                                        </button>
                                        <button
                                            type="button"
                                            className="px-3 py-2 text-xs font-semibold text-gray-300 bg-gray-700 border border-gray-600 hover:bg-gray-600"
                                            style={{ borderRadius: themeColors.borderRadius.component }}
                                            onClick={handleRemoveCover}
                                            disabled={coverMode === 'none'}
                                        >
                                            Remove cover
                                        </button>
                                    </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>
            </AudioContent>
        </AudioPlayerContainer>
    );
};

interface MediaViewerProps {
    uuid: string;
    filePath: string;
    fileType: 'image' | 'video' | 'audio';
}

const MediaViewer: React.FC<MediaViewerProps> = ({ uuid, filePath, fileType }) => {
    const [fileUrl, setFileUrl] = useState<string | null>(null);
    const [loading, setLoading] = useState(true);
    const [buffering, setBuffering] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [blobUrl, setBlobUrl] = useState<string | null>(null);
    const [mediaBlob, setMediaBlob] = useState<Blob | null>(null);
    const [blobPartial, setBlobPartial] = useState(false);
    const [downloadProgress, setDownloadProgress] = useState(0);
    const [isDownloading, setIsDownloading] = useState(false);
    const [editMetadataRequest, setEditMetadataRequest] = useState(0);
    const xhrRef = React.useRef<XMLHttpRequest | null>(null);
    const blobUrlRef = React.useRef<string | null>(null);
    const downloadUrlRef = React.useRef<string | null>(null);
    const [streamMode, setStreamMode] = useState(false);
    const [streamFallbackTried, setStreamFallbackTried] = useState(false);
    const streamTimeRef = React.useRef(0);
    const streamPlayingRef = React.useRef(false);
    const [resumeState, setResumeState] = useState<{ time: number; wasPlaying: boolean; token: number } | null>(null);
    const MAX_FILE_SIZE = 250 * 1024 * 1024;
    const STREAM_REFRESH_MS = 10 * 60 * 1000;
    useEffect(() => {
        return () => {
            if (xhrRef.current) {
                xhrRef.current.abort();
            }
            if (blobUrlRef.current) {
                URL.revokeObjectURL(blobUrlRef.current);
                blobUrlRef.current = null;
            }
            downloadUrlRef.current = null;
        };
    }, [uuid, filePath]);

    const startBlobDownload = useCallback((url: string) => {
        if (xhrRef.current) {
            xhrRef.current.abort();
        }
        setStreamMode(false);
        setBlobPartial(false);
        setIsDownloading(true);
        setLoading(true);
        setError(null);
        setDownloadProgress(0);
        setMediaBlob(null);
        setBlobUrl(null);
        setFileUrl(null);

        const xhr = new XMLHttpRequest();
        xhrRef.current = xhr;
        xhr.open('GET', url);
        xhr.responseType = 'blob';

        xhr.onprogress = (e) => {
            if (e.lengthComputable) {
                if (e.total > MAX_FILE_SIZE) {
                    xhr.abort();
                    setError(`File is too large to preview (${Math.round(e.total / 1024 / 1024)}MB). Max size is 250MB.`);
                    setIsDownloading(false);
                    setLoading(false);
                    return;
                }
                setDownloadProgress((e.loaded / e.total) * 100);
            }
        };

        xhr.onload = () => {
            if (xhr.status === 200) {
                const blob = xhr.response;
                if (blob.size < 10240) {
                    const reader = new FileReader();
                    reader.onload = () => {
                        const text = reader.result as string;
                        if (text.includes('{"') || text.includes('<!DOCTYPE') || text.includes('<html')) {
                            setError('Failed to download media: ' + (text.substring(0, 100) + '...'));
                        } else {
                            processBlob(blob);
                        }
                    };
                    reader.readAsText(blob);
                    return;
                }

                processBlob(blob);
            } else {
                setError('Failed to download media file (Status: ' + xhr.status + ')');
                setIsDownloading(false);
                setLoading(false);
            }
        };

        const processBlob = (blob: Blob) => {
            const ext = filePath.split('.').pop()?.toLowerCase();
            let mimeType = blob.type;

            if (ext === 'mp4') mimeType = 'video/mp4';
            else if (ext === 'webm') mimeType = 'video/webm';
            else if (ext === 'ogg') mimeType = 'video/ogg';
            else if (ext === 'mp3') mimeType = 'audio/mpeg';
            else if (ext === 'wav') mimeType = 'audio/wav';

            const newBlob = new Blob([blob], { type: mimeType });
            const objectUrl = URL.createObjectURL(newBlob);
            blobUrlRef.current = objectUrl;

            setMediaBlob(newBlob);
            setBlobPartial(false);
            setBlobUrl(objectUrl);
            setFileUrl(objectUrl);
            setIsDownloading(false);
            setLoading(false);
        };

        xhr.onerror = () => {
            setError('Network error while downloading media.');
            setIsDownloading(false);
            setLoading(false);
        };

        xhr.send();
    }, [filePath]);

    const requestFullDownload = useCallback(() => {
        if (isDownloading) return;
        if (downloadUrlRef.current) {
            startBlobDownload(downloadUrlRef.current);
            return;
        }
        getFileDownloadUrl(uuid, filePath)
            .then((url) => {
                downloadUrlRef.current = url;
                startBlobDownload(url);
            })
            .catch(() => {
            });
    }, [filePath, isDownloading, startBlobDownload, uuid]);

    const refreshStreamUrl = useCallback(() => {
        setResumeState({
            time: streamTimeRef.current,
            wasPlaying: streamPlayingRef.current,
            token: Date.now(),
        });
        return getFileStreamUrl(uuid, filePath)
            .then((url) => {
                setStreamMode(true);
                setStreamFallbackTried(false);
                setFileUrl(url);
            });
    }, [uuid, filePath]);

    useEffect(() => {
        if (xhrRef.current) {
            xhrRef.current.abort();
        }
        setLoading(true);
        setError(null);
        setBlobUrl(null);
        setMediaBlob(null);
        setBlobPartial(false);
        setDownloadProgress(0);
        setIsDownloading(false);
        setStreamMode(false);
        setStreamFallbackTried(false);
        if (blobUrlRef.current) {
            URL.revokeObjectURL(blobUrlRef.current);
            blobUrlRef.current = null;
        }

        if (fileType === 'image') {
            getFileDownloadUrl(uuid, filePath)
                .then(url => {
                    downloadUrlRef.current = url;
                    setFileUrl(url);
                    setLoading(false);
                })
                .catch(err => {
                    setError(err.message || 'Failed to load media file');
                    setLoading(false);
                });
            return;
        }

        const tryStream = fileType === 'video' || fileType === 'audio';
        if (tryStream) {
            getFileStreamUrl(uuid, filePath)
                .then(url => {
                    setStreamMode(true);
                    setFileUrl(url);
                    setLoading(false);
                })
                .catch(() => {
                    getFileDownloadUrl(uuid, filePath)
                        .then(url => {
                            downloadUrlRef.current = url;
                            startBlobDownload(url);
                        })
                        .catch(err => {
                            setError(err.message || 'Failed to load media file');
                            setLoading(false);
                        });
                });
            return;
        }

        getFileDownloadUrl(uuid, filePath)
            .then(url => {
                downloadUrlRef.current = url;
                startBlobDownload(url);
            })
            .catch(err => {
                setError(err.message || 'Failed to load media file');
                setLoading(false);
            });
    }, [uuid, filePath, fileType, startBlobDownload]);

    useEffect(() => {
        if (!streamMode) return;
        const interval = setInterval(() => {
            refreshStreamUrl().catch(() => {
            });
        }, STREAM_REFRESH_MS);
        return () => clearInterval(interval);
    }, [streamMode, refreshStreamUrl, STREAM_REFRESH_MS]);

    useEffect(() => {
        const maybeTrigger = (path?: string) => {
            if (fileType !== 'audio') return;
            if (!path || path !== filePath) return;
            setEditMetadataRequest(Date.now());
            if (streamMode || blobPartial || !mediaBlob) {
                requestFullDownload();
            }
        };

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

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

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

    const handleMediaError = (e: React.SyntheticEvent<HTMLVideoElement | HTMLAudioElement>) => {
        const mediaElement = e.currentTarget;
        const error = mediaElement.error;

        if (!error) {
            return;
        }

        if (streamMode && !streamFallbackTried) {
            setStreamFallbackTried(true);
            refreshStreamUrl()
                .then(() => {})
                .catch(() => {
                    if (downloadUrlRef.current) {
                        startBlobDownload(downloadUrlRef.current);
                        return;
                    }
                    getFileDownloadUrl(uuid, filePath)
                        .then(url => {
                            downloadUrlRef.current = url;
                            startBlobDownload(url);
                        })
                        .catch(err => {
                            setError(err.message || 'Failed to load media file');
                            setLoading(false);
                        });
                });
            return;
        }

        console.error('[MediaViewer] Media error:', {
            code: error.code,
            message: error.message,
            filePath,
            fileUrl,
        });

        const errorMessages: Record<number, string> = {
            1: 'Media loading aborted by user',
            2: 'Network error while loading media - check your connection',
            3: 'Media decoding failed - file may be corrupted',
            4: 'Media format not supported by your browser',
        };

        if (error.code !== 1) {
            setError(errorMessages[error.code] || `Media error (code: ${error.code})`);
        }
    };

    const isFormatSupported = (path: string) => {
        const ext = path.split('.').pop()?.toLowerCase() || '';
        return ['mp4', 'webm', 'ogg', 'mp3', 'wav', 'flac', 'm4a'].includes(ext);
    };

    if (loading || isDownloading) {
        return (
            <ViewerWrapper>
                <ViewerContainer>
                    <div className="flex-1 flex flex-col items-center justify-center p-6">
                        <Spinner size="large" />
                        {isDownloading && (
                            <div className="mt-4 w-64">
                                <div className="flex justify-between text-xs text-neutral-300 mb-1">
                                    <span>Downloading for preview...</span>
                                    <span>{Math.round(downloadProgress)}%</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: `${downloadProgress}%` }}
                                    />
                                </div>
                                <p className="text-xs text-neutral-300 mt-2 text-center">
                                    Large files are downloaded to memory for smooth playback.
                                </p>
                            </div>
                        )}
                    </div>
                </ViewerContainer>
            </ViewerWrapper>
        );
    }

    if (error || !fileUrl) {
        return (
            <ViewerWrapper>
                <ViewerContainer>
                    <div className="flex-1 flex flex-col items-center justify-center text-red-400 text-center max-w-md p-6">
                        <svg className="w-16 h-16 mx-auto mb-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                        </svg>
                        <p className="text-lg font-semibold mb-2">Playback Failed</p>
                        <p className="text-sm text-neutral-300 mb-4">
                            {error?.includes('DEMUXER_ERROR')
                                ? 'The video format or codec is not supported by your browser.'
                                : (error || 'Unknown error')}
                        </p>

                        {blobUrl && (
                            <a
                                href={blobUrl!}
                                download={filePath.split('/').pop()}
                                className="inline-flex items-center px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white transition-colors duration-200 text-sm font-medium"
                                style={{ borderRadius: themeColors.borderRadius.component }}
                            >
                                <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
                                </svg>
                                Download to View Locally
                            </a>
                        )}

                        {!isFormatSupported(filePath) && (
                            <p className="text-xs text-yellow-500 mt-4">
                                Note: The file extension ".{filePath.split('.').pop()}" might not be supported by your browser.
                            </p>
                        )}
                        <p className="text-xs text-neutral-300 mt-4">File: {filePath}</p>
                    </div>
                </ViewerContainer>
            </ViewerWrapper>
        );
    }

    return (
        <ViewerWrapper>
            <ViewerContainer>
                {fileType === 'image' && (
                    <div className="flex-1 flex items-center justify-center">
                        <Image src={fileUrl} alt={filePath} />
                    </div>
                )}
                {fileType === 'video' && (
                    <>
                        <CustomVideoPlayer
                            src={fileUrl}
                            blobUrl={blobUrl}
                            fileName={filePath.split('/').pop()}
                            isStreaming={streamMode}
                            resumeState={resumeState}
                            onError={handleMediaError}
                            onWaiting={() => setBuffering(true)}
                            onPlaying={() => setBuffering(false)}
                            onTimeUpdate={(time: number) => {
                                streamTimeRef.current = time;
                            }}
                            onPlayingChange={(playing: boolean) => {
                                streamPlayingRef.current = playing;
                            }}
                        />
                        {buffering && (
                            <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
                                <Spinner size="large" />
                            </div>
                        )}
                    </>
                )}
                {fileType === 'audio' && (
                    <CustomAudioPlayer
                        src={fileUrl}
                        blobUrl={blobUrl}
                        blob={mediaBlob}
                        blobPartial={blobPartial}
                        isStreaming={streamMode}
                        resumeState={resumeState}
                        filePath={filePath}
                        uuid={uuid}
                        fileName={filePath.split('/').pop()}
                        onError={handleMediaError}
                        onTimeUpdate={(time: number) => {
                            streamTimeRef.current = time;
                        }}
                        onPlayingChange={(playing: boolean) => {
                            streamPlayingRef.current = playing;
                        }}
                        onRequestFullDownload={requestFullDownload}
                        editMetadataRequest={editMetadataRequest}
                    />
                )}
            </ViewerContainer>
        </ViewerWrapper>
    );
};

export default MediaViewer;
