'use client'

import Image from 'next/image'
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import {
    useCallback,
    useEffect,
    useMemo,
    useRef,
    useState,
    type MouseEvent as ReactMouseEvent,
    type ReactNode,
} from 'react'
import Hls from 'hls.js'
import {
    FiCheck,
    FiChevronLeft,
    FiChevronRight,
    FiMaximize,
    FiMessageSquare,
    FiMinimize,
    FiSettings,
    FiVolume2,
    FiVolumeX,
} from 'react-icons/fi'
import { BsTv, BsTvFill } from 'react-icons/bs'
import { FaPause, FaPlay, FaUser } from 'react-icons/fa'
import { MdPictureInPictureAlt } from 'react-icons/md'
import {
    formatLastStreamAtLabel,
    formatLiveDuration,
    formatStreamStartedAtDateTime,
    formatViews,
} from '@/utils/format'
import { useLiveCatchup } from '@/hooks/useLiveCatchup'
import LikeFollowActionGroup from '@/app/room/_components/LikeFollowActionGroup'
import { isWhepPlaybackUrl, resolveWhepPlaybackUrl } from '@/lib/streams/cloudflareStreamUrls'
import { startWhepPlayback, type WhepPlaybackHandle } from '@/lib/streams/whepPlayback'
import {
    startWebRtcSignalMonitor,
    type WebRtcSignalSnapshot,
} from '@/lib/media/webrtcSignalMonitor'
import {
    collectVideoPlaybackStats,
    collectWebRtcPlaybackStats,
    createStreamStatsAccumulator,
    emptyStreamPlaybackStats,
    enrichStreamPlaybackStats,
    type StreamPlaybackStats,
} from '@/lib/media/streamPlaybackStats'
import {
    exitVideoFullscreen,
    FULLSCREEN_CHANGE_EVENTS,
    isVideoFullscreenActive,
    requestVideoFullscreen,
} from '@/lib/media/videoFullscreen'
import PlayerContextMenu from '@/app/room/_components/PlayerContextMenu'
import PlayerStreamStatsMenu from '@/app/room/_components/PlayerStreamStatsMenu'
import LiveStreamLoadingMedia from '@/components/LiveStreamLoadingMedia'

function isHlsSource(src: string): boolean {
    const clean = src.split('#')[0]?.split('?')[0]?.toLowerCase() ?? ''
    return clean.endsWith('.m3u8')
}

/** 主播後台僅比對 video UID，避免 startedAt 同步觸發重打 webRTC/play */
function playbackSessionCompareKey(sessionId: string, uidOnly: boolean): string {
    const id = sessionId.trim().toLowerCase()
    if (!uidOnly) return id
    const colon = id.indexOf(':')
    if (colon > 0) return id.slice(0, colon)
    return id
}

/** 直播中斷線/404/500 時 hls.js 會重試，不代表尚未開播 */
function attachHlsErrorHandler(
    hls: Hls,
    getIsLiveMode: () => boolean,
    onPlaybackUnavailable: () => void
) {
    hls.on(Hls.Events.ERROR, (_, data: any) => {
        if (getIsLiveMode()) {
            if (data.fatal) {
                switch (data.type) {
                    case Hls.ErrorTypes.NETWORK_ERROR:
                        try {
                            hls.startLoad()
                        } catch {
                            // ignore
                        }
                        break
                    case Hls.ErrorTypes.MEDIA_ERROR:
                        try {
                            hls.recoverMediaError()
                        } catch {
                            // ignore
                        }
                        break
                    default:
                        try {
                            hls.recoverMediaError()
                        } catch {
                            // ignore
                        }
                        break
                }
            }
            return
        }

        const code = data?.response?.code ?? data?.response?.status
        const isNetwork = data?.type === Hls.ErrorTypes.NETWORK_ERROR
        const isManifestOrLevelError =
            data.details === Hls.ErrorDetails.MANIFEST_LOAD_ERROR ||
            data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR
        if ((isNetwork && isManifestOrLevelError) || (typeof code === 'number' && code >= 400)) {
            onPlaybackUnavailable()
        }
    })
}

type Props = {
    src: string
    isLive?: boolean
    className?: string
    poster?: string
    useDefaultControls?: boolean
    showQualityMenu?: boolean
    onReady?: (player: HTMLVideoElement) => void
    onDetectedLiveChange?: (isLive: boolean) => void
    showChatReopenButton?: boolean
    onChatReopen?: () => void
    chatReopenLabel?: string
    isTheater?: boolean
    onToggleTheater?: () => void
    /** 為 false 時隱藏劇院模式按鈕（例如主播後台預覽） */
    showTheaterToggle?: boolean
    theaterInfo?: {
        avatar: string
        channelName: string
        title: string
        viewers: number
        followers?: number
        startedAt?: string
    }
    onStreamerClick?: (e: ReactMouseEvent<HTMLButtonElement>) => void
    isFollowing?: boolean
    isLiked?: boolean
    likeCount?: number
    onToggleFollow?: () => void
    onToggleLike?: () => void
    isOwnStream?: boolean
    /** 為 true 時影片容器使用 min-h-full 以填滿父層高度（例如手機直播間） */
    fillHeight?: boolean
    /** 為 true 時隱藏底部自訂控制列，並禁止使用者暫停/播放、快轉、調音量等（仍保留程式端播放控制） */
    disableUserControls?: boolean
    /** 為 false 時離線狀態不顯示「主播尚未開播」提示（例如儀表板預覽） */
    showNotLiveLabel?: boolean
    /** 直播播放：`auto` 優先 WebRTC（WHEP），失敗回落 HLS；`hls` 僅 HLS；`webrtc` 僅 WHEP */
    playbackTransport?: 'auto' | 'hls' | 'webrtc'
    /** lifecycle videoUID；變更時（OBS 重連新場次）強制重拉 WHEP */
    playbackSessionId?: string | null
    /** 儀表板預覽：hover 與底部 toolbar 同步顯示中央「進入直播間」 */
    dashboardPreviewEnter?: { href: string }
    /** 儀表板預覽疊層（例如離線提示），須低於中央進房按鈕 */
    dashboardPreviewOverlay?: ReactNode
    /** WebRTC 播放時輪詢 getStats，回報訊號格數（儀表板 Wi‑Fi 圖示） */
    onWebRtcSignalChange?: (snapshot: WebRtcSignalSnapshot | null) => void
    /** 為 false 時停用右鍵串流資訊選單 */
    enableStreamStatsMenu?: boolean
}

type HlsLevel = {
    height?: number
    width?: number
    bitrate?: number
    name?: string
}

type QualityOption =
    | { key: 'auto'; label: string }
    | { key: 'level'; label: string; height: number }

function cx(...classes: Array<string | false | null | undefined>) {
    return classes.filter(Boolean).join(' ')
}

function clamp(n: number, min: number, max: number) {
    return Math.min(max, Math.max(min, n))
}


function formatTime(sec: number) {
    if (!Number.isFinite(sec) || sec < 0) return '00:00'
    const s = Math.floor(sec)
    const h = Math.floor(s / 3600)
    const m = Math.floor((s % 3600) / 60)
    const ss = s % 60
    if (h > 0) return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(ss).padStart(2, '0')}`
    return `${String(m).padStart(2, '0')}:${String(ss).padStart(2, '0')}`
}

type PlayerIconButtonProps = {
    tip?: string
    onClick?: () => void
    disabled?: boolean
    ariaLabel: string
    children: React.ReactNode
    className?: string
    tipSide?: 'top' | 'bottom'
}

function PlayerIconButton({
    tip,
    onClick,
    disabled,
    ariaLabel,
    children,
    className,
    tipSide = 'top',
}: PlayerIconButtonProps) {
    return (
        <button
            type="button"
            onClick={(e) => {
                e.stopPropagation()
                onClick?.()
            }}
            disabled={disabled}
            aria-label={ariaLabel}
            className={cx(
                'relative group inline-flex items-center justify-center',
                'w-9 h-9 rounded-lg',
                'text-white/90 hover:text-white',
                'transition-colors',
                disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
                className
            )}
        >
            <span className="absolute inset-0 rounded-lg transition-colors bg-white/0 group-hover:bg-white/10 group-active:bg-white/15" />
            <span className="relative z-[1]">{children}</span>

            {tip && (
                <span
                    className={cx(
                        'pointer-events-none absolute left-1/2 -translate-x-1/2',
                        'whitespace-nowrap text-xs font-semibold leading-none',
                        'opacity-0 translate-y-1 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-150',
                        tipSide === 'top' ? 'bottom-full mb-2' : 'top-full mt-2'
                    )}
                >
                    <span className="relative block">
                        <span className="block rounded-md bg-[#1f1f23]/90 px-2.5 py-2 text-white shadow-lg">
                            {tip}
                        </span>
                        <span
                            className={cx(
                                'absolute left-1/2 -translate-x-1/2 w-2 h-2 rotate-45 bg-[#1f1f23]/90',
                                tipSide === 'top' ? 'top-full -mt-1' : 'bottom-full -mb-1'
                            )}
                        />
                    </span>
                </span>
            )}
        </button>
    )
}

export default function RoomVideoPlayer({
    src,
    isLive = true,
    className,
    poster,
    useDefaultControls = false,
    showQualityMenu = true,
    onReady,
    onDetectedLiveChange,
    showChatReopenButton = false,
    onChatReopen,
    chatReopenLabel = '',
    isTheater = false,
    onToggleTheater,
    showTheaterToggle = true,
    theaterInfo,
    onStreamerClick,
    isFollowing,
    isLiked,
    likeCount,
    onToggleFollow,
    onToggleLike,
    isOwnStream = false,
    fillHeight = false,
    disableUserControls = false,
    showNotLiveLabel = true,
    playbackTransport = 'auto',
    playbackSessionId = null,
    dashboardPreviewEnter,
    dashboardPreviewOverlay,
    onWebRtcSignalChange,
    enableStreamStatsMenu = true,
}: Props) {
    const { t } = useTranslation()
    const dashboardPreviewEnterMode = Boolean(dashboardPreviewEnter)
    const controlsHoverEnabled = !disableUserControls || dashboardPreviewEnterMode
    const chatLabel = chatReopenLabel || t('room.openChat')
    const qualityAutoLabel = t('room.player.qualityAuto', { defaultValue: 'Auto' })
    const qualityLabel = t('room.player.quality', { defaultValue: 'Quality' })
    const qualityHeightLabel = useCallback(
        (height: number) => t('room.player.qualityHeight', { height, defaultValue: `${height}p` }),
        [t]
    )
    const hlsPlayback = useMemo(() => isHlsSource(src), [src])

    const onReadyRef = useRef<Props['onReady']>(onReady)
    useEffect(() => {
        playbackTransportRef.current = playbackTransport
    }, [playbackTransport])
    useEffect(() => {
        onReadyRef.current = onReady
    }, [onReady])

    const containerRef = useRef<HTMLDivElement | null>(null)
    const mountRef = useRef<HTMLDivElement | null>(null)
    const videoElRef = useRef<HTMLVideoElement | null>(null)
    const hlsRef = useRef<Hls | null>(null)
    const whepHandleRef = useRef<WhepPlaybackHandle | null>(null)
    const startLiveStreamRef = useRef<(() => Promise<void>) | null>(null)
    const stopWhepPlaybackRef = useRef<(() => Promise<void>) | null>(null)
    const reconnectLivePlaybackRef = useRef<(() => Promise<void>) | null>(null)
    const scheduleReconnectLivePlaybackRef = useRef<((forceFull?: boolean) => void) | null>(null)
    const lastPlaybackSessionRef = useRef<string | null>(null)
    const lastLiveReconnectAtRef = useRef(0)
    const reconnectInFlightRef = useRef(false)
    const playbackModeRef = useRef<'hls' | 'webrtc' | null>(null)
    const playbackTransportRef = useRef(playbackTransport)
    const isOwnStreamRef = useRef(isOwnStream)
    useEffect(() => {
        isOwnStreamRef.current = isOwnStream
    }, [isOwnStream])
    const [playbackMode, setPlaybackMode] = useState<'hls' | 'webrtc'>('hls')
    const [ready, setReady] = useState(false)
    const [isPlaying, setIsPlaying] = useState(false)
    const isPlayingRef = useRef(false)
    const [centerToastVisible, setCenterToastVisible] = useState(false)
    const [centerToastIcon, setCenterToastIcon] = useState<'play' | 'pause'>('play')
    const centerToastTimerRef = useRef<number | null>(null)
    const [duration, setDuration] = useState<number>(0)
    const [currentTime, setCurrentTime] = useState<number>(0)
    const [volume, setVolume] = useState<number>(1)
    const [muted, setMuted] = useState<boolean>(true)
    const volumeRef = useRef(volume)
    const mutedRef = useRef(muted)
    /** 使用者已手動調整音量／靜音後，不再被 ensureAutoplay 強制靜音 */
    const userAudioUnlockedRef = useRef(false)
    const [isFullscreen, setIsFullscreen] = useState<boolean>(false)
    const isFullscreenRef = useRef(false)
    const pointerInsideRef = useRef(false)
    const pendingSeekToRef = useRef<number | null>(null)
    const lastSeekRetryAtRef = useRef<number>(0)
    const [notLive, setNotLive] = useState(() => !isLive)
    const liveVideoReadyRef = useRef(false)
    const [liveVideoReady, setLiveVideoReady] = useState(false)

    const hideControlsTimerRef = useRef<number | null>(null)
    const [controlsVisible, setControlsVisible] = useState(!dashboardPreviewEnterMode)
    const [playerContextMenu, setPlayerContextMenu] = useState<{ x: number; y: number } | null>(
        null
    )
    const [streamStatsOpen, setStreamStatsOpen] = useState(false)
    const [streamStats, setStreamStats] = useState<StreamPlaybackStats>(() => emptyStreamPlaybackStats())
    const streamStatsAccRef = useRef(createStreamStatsAccumulator())

    const [seekingPercent, setSeekingPercent] = useState<number | null>(null)
    const [liveNowMs, setLiveNowMs] = useState<number>(() => Date.now())
    const [isLiveByPlayer, setIsLiveByPlayer] = useState(false)
    const isLiveByPlayerRef = useRef(false)
    const onDetectedLiveChangeRef = useRef<Props['onDetectedLiveChange']>(onDetectedLiveChange)
    useEffect(() => {
        onDetectedLiveChangeRef.current = onDetectedLiveChange
    }, [onDetectedLiveChange])
    useEffect(() => {
        volumeRef.current = volume
    }, [volume])
    useEffect(() => {
        mutedRef.current = muted
    }, [muted])


    const getSeekableRange = useCallback(() => {
        const videoEl = videoElRef.current
        if (!videoEl) return null

        let seekable: TimeRanges | null = null
        try {
            seekable = videoEl.seekable
        } catch {
            // ignore errors when accessing seekable
        }

        const playerDuration = videoEl.duration

        if (seekable && seekable.length > 0) {
            try {
                const lastIndex = seekable.length - 1
                if (lastIndex >= 0) {
                    const start = seekable.start(0)
                    const end = seekable.end(lastIndex)
                    if (Number.isFinite(start) && Number.isFinite(end) && end > start) {
                        return { start, end }
                    }
                }
            } catch {
                // ignore errors when accessing seekable ranges
            }
        }

        if (typeof playerDuration === 'number' && Number.isFinite(playerDuration) && playerDuration > 0) {
            return { start: 0, end: playerDuration }
        }

        if (Number.isFinite(duration) && duration > 0) return { start: 0, end: duration }
        return null
    }, [duration])

    const progressValue = useMemo(() => {
        if (seekingPercent !== null) return clamp(seekingPercent, 0, 100)

        const range = getSeekableRange()
        if (!range) return 0

        const span = range.end - range.start
        if (!Number.isFinite(span) || span <= 0) return 0

        return clamp(((currentTime - range.start) / span) * 100, 0, 100)
    }, [currentTime, getSeekableRange, seekingPercent])

    const [qualities, setQualities] = useState<QualityOption[]>([{ key: 'auto', label: qualityAutoLabel }])
    const [active, setActive] = useState<'auto' | number>('auto')
    const [settingsOpen, setSettingsOpen] = useState(false)
    const [settingsPanel, setSettingsPanel] = useState<'main' | 'quality'>('main')
    const settingsWrapRef = useRef<HTMLDivElement | null>(null)
    const [isPip, setIsPip] = useState(false)
    const qualityAutoLabelRef = useRef(qualityAutoLabel)
    const qualityHeightLabelRef = useRef(qualityHeightLabel)
    useEffect(() => {
        qualityAutoLabelRef.current = qualityAutoLabel
    }, [qualityAutoLabel])
    useEffect(() => {
        qualityHeightLabelRef.current = qualityHeightLabel
    }, [qualityHeightLabel])

    const syncQualitiesFromHls = useCallback(() => {
        const hls = hlsRef.current
        if (!hls?.levels?.length) {
            setQualities([{ key: 'auto', label: qualityAutoLabelRef.current }])
            return
        }

        const heights = new Set<number>()
        for (let i = 0; i < hls.levels.length; i++) {
            const h = hls.levels[i]?.height
            if (typeof h === 'number' && h > 0) heights.add(h)
        }

        const list: QualityOption[] = [{ key: 'auto', label: qualityAutoLabelRef.current }]
        Array.from(heights)
            .sort((a, b) => b - a)
            .forEach((h) => list.push({ key: 'level', label: qualityHeightLabelRef.current(h), height: h }))
        setQualities(list)

        if (hls.currentLevel === -1) {
            setActive('auto')
        } else {
            const levelHeight = hls.levels[hls.currentLevel]?.height
            if (typeof levelHeight === 'number') setActive(levelHeight)
        }
    }, [])

    const attachHlsQualityHandlers = useCallback(
        (hls: Hls) => {
            hls.on(Hls.Events.LEVEL_SWITCHED, syncQualitiesFromHls)
        },
        [syncQualitiesFromHls]
    )

    const activeQualityLabel = useMemo(() => {
        if (active === 'auto') return qualityAutoLabel
        const match = qualities.find((q) => q.key === 'level' && q.height === active)
        return match?.label ?? qualityHeightLabel(active)
    }, [active, qualities, qualityAutoLabel, qualityHeightLabel])

    const isLiveRef = useRef(isLive)
    useEffect(() => {
        isLiveRef.current = isLive
    }, [isLive])

    const resetLiveVideoReady = useCallback(() => {
        liveVideoReadyRef.current = false
        setLiveVideoReady(false)
    }, [])

    const tryMarkLiveVideoReady = useCallback(() => {
        if (!isLiveRef.current || liveVideoReadyRef.current) return false
        const el = videoElRef.current
        if (!el) return false
        try {
            if (el.readyState < 2 || el.videoWidth <= 0 || el.videoHeight <= 0) return false
        } catch {
            return false
        }
        liveVideoReadyRef.current = true
        setLiveVideoReady(true)
        return true
    }, [])

    const tryMarkLiveVideoReadyRef = useRef(tryMarkLiveVideoReady)
    useEffect(() => {
        tryMarkLiveVideoReadyRef.current = tryMarkLiveVideoReady
    }, [tryMarkLiveVideoReady])

    const effectiveIsLive = isLive
    const showLiveStreamLoading = effectiveIsLive && !liveVideoReady

    /** 開播後 WHEP 可能尚未就緒，定時重試直到連上或畫面出現 */
    useEffect(() => {
        if (!isLive || !ready) return
        if (playbackTransport === 'hls') return
        const whepUrl = resolveWhepPlaybackUrl(src)
        if (!whepUrl) return

        let cancelled = false
        let connectFailures = 0
        let whepWaitingSince: number | null = null
        let timeoutId: number | null = null

        const nextDelayMs = () => {
            const base = isOwnStream ? 5000 : 3000
            if (connectFailures <= 1) return base
            return Math.min(base * 1.5 ** (connectFailures - 1), isOwnStream ? 20_000 : 30_000)
        }

        const retry = () => {
            if (cancelled || !isLiveRef.current || reconnectInFlightRef.current) return
            if (liveVideoReadyRef.current) {
                connectFailures = 0
                whepWaitingSince = null
                if (playbackModeRef.current === 'webrtc' && whepHandleRef.current) {
                    ensureMutedAutoplayRef.current()
                }
                return
            }
            if (playbackModeRef.current === 'webrtc' && whepHandleRef.current) {
                ensureMutedAutoplayRef.current()
                tryMarkLiveVideoReadyRef.current()
                if (whepWaitingSince == null) whepWaitingSince = Date.now()
                const waitedMs = Date.now() - whepWaitingSince
                // 已建立 WebRTC 連線：先等首幀，勿每幾秒重打 webRTC/play
                if (waitedMs < 20_000) return
                scheduleReconnectLivePlaybackRef.current?.(false)
                whepWaitingSince = Date.now()
                return
            }
            whepWaitingSince = null
            connectFailures += 1
            void startLiveStreamRef.current?.()
        }

        const scheduleNext = () => {
            if (cancelled) return
            timeoutId = window.setTimeout(() => {
                retry()
                scheduleNext()
            }, nextDelayMs())
        }

        timeoutId = window.setTimeout(() => {
            retry()
            scheduleNext()
        }, 1500)

        return () => {
            cancelled = true
            if (timeoutId != null) window.clearTimeout(timeoutId)
        }
    }, [isLive, ready, src, playbackTransport, isOwnStream])

    useEffect(() => {
        const sessionId = playbackSessionId?.trim().toLowerCase() || null
        if (sessionId == null) return
        const compareKey = playbackSessionCompareKey(sessionId, isOwnStreamRef.current)
        if (lastPlaybackSessionRef.current === compareKey) return
        const hadPrevious = lastPlaybackSessionRef.current != null
        lastPlaybackSessionRef.current = compareKey
        if (!hadPrevious || !isLive || !ready) return
        resetLiveVideoReady()
        scheduleReconnectLivePlaybackRef.current?.(isOwnStreamRef.current ? false : true)
    }, [playbackSessionId, isLive, ready, resetLiveVideoReady, isOwnStream])

    /** WebRTC 畫面停滯（OBS 斷線重連但 ICE 仍 connected）時重拉流 */
    useEffect(() => {
        if (isOwnStream || !isLive || !ready || playbackMode !== 'webrtc') return
        const video = videoElRef.current
        if (!video) return

        const STALL_MS = 8_000
        let lastFrameAt = Date.now()
        let lastTime = video.currentTime
        let rvfcId: number | null = null
        let cancelled = false

        const markFrameProgress = () => {
            lastFrameAt = Date.now()
        }

        const onTimeUpdate = () => {
            const t = video.currentTime
            if (Number.isFinite(t) && t !== lastTime) {
                lastTime = t
                markFrameProgress()
            }
        }
        video.addEventListener('timeupdate', onTimeUpdate)

        const scheduleRvfc = () => {
            if (cancelled || typeof video.requestVideoFrameCallback !== 'function') return
            rvfcId = video.requestVideoFrameCallback(() => {
                markFrameProgress()
                scheduleRvfc()
            })
        }
        scheduleRvfc()

        const intervalId = window.setInterval(() => {
            if (!isLiveRef.current || !liveVideoReadyRef.current || !whepHandleRef.current) return
            if (Date.now() - lastFrameAt < STALL_MS) return
            resetLiveVideoReady()
            scheduleReconnectLivePlaybackRef.current?.(false)
        }, 3000)

        return () => {
            cancelled = true
            video.removeEventListener('timeupdate', onTimeUpdate)
            if (rvfcId != null && typeof video.cancelVideoFrameCallback === 'function') {
                try {
                    video.cancelVideoFrameCallback(rvfcId)
                } catch {
                    // ignore
                }
            }
            window.clearInterval(intervalId)
        }
    }, [isLive, ready, playbackMode, resetLiveVideoReady, isOwnStream])

    useEffect(() => {
        const notify = onWebRtcSignalChange
        if (!notify) return
        if (!isLive || playbackMode !== 'webrtc') {
            notify(null)
            return
        }
        const stopMonitor = startWebRtcSignalMonitor(
            () => {
                const pc = whepHandleRef.current?.peerConnection ?? null
                if (!pc || pc.connectionState === 'closed') return null
                return pc
            },
            {
                onUpdate: (snapshot) => notify(snapshot),
                onInactive: () => notify(null),
            }
        )
        return () => {
            stopMonitor()
            notify(null)
        }
    }, [isLive, playbackMode, onWebRtcSignalChange, src])

    useLiveCatchup(videoElRef, {
        targetLatency: 2.5,
        maxLatency: 6,
        catchupRate: 1.05,
        enabled: effectiveIsLive && playbackMode === 'hls',
    })

    useEffect(() => {
        if (!theaterInfo?.startedAt) return
        setLiveNowMs(Date.now())
        const id = window.setInterval(() => setLiveNowMs(Date.now()), 1000)
        return () => window.clearInterval(id)
    }, [theaterInfo?.startedAt])

    const theaterStartedAtTitle = useMemo(
        () => formatStreamStartedAtDateTime(theaterInfo?.startedAt),
        [theaterInfo?.startedAt]
    )

    const seekToLiveEdge = useCallback(() => {
        const videoEl = videoElRef.current
        if (!videoEl) return

        const range = getSeekableRange()
        if (!range) return
        const target = range.end
        if (!Number.isFinite(target)) return
        pendingSeekToRef.current = target
        lastSeekRetryAtRef.current = performance.now()
        setCurrentTime(target)
        try {
            videoEl.currentTime = target
        } catch {
            // ignore
        }
    }, [getSeekableRange])

    const playFromLiveEdge = useCallback(() => {
        const videoEl = videoElRef.current
        if (!videoEl) return
        if (effectiveIsLive && videoEl.paused) {
            seekToLiveEdge()
        }
        try {
            const r = videoEl.play()
            if (r && typeof r.catch === 'function') r.catch(() => { })
        } catch {
            // ignore
        }
    }, [effectiveIsLive, seekToLiveEdge])

    const togglePlay = useCallback(() => {
        const videoEl = videoElRef.current
        if (!videoEl) return
        if (effectiveIsLive && !videoEl.paused) return
        if (videoEl.paused) playFromLiveEdge()
        else {
            try {
                videoEl.pause()
            } catch {
                // ignore
            }
        }
    }, [effectiveIsLive, playFromLiveEdge])

    const ensureMutedAutoplay = useCallback(() => {
        const videoEl = videoElRef.current
        if (!videoEl) return
        const targetVolume = volumeRef.current > 0 ? volumeRef.current : 1
        const respectUserAudio = userAudioUnlockedRef.current
        try {
            if (respectUserAudio) {
                videoEl.muted = mutedRef.current
                if (mutedRef.current) {
                    videoEl.setAttribute('muted', '')
                } else {
                    videoEl.removeAttribute('muted')
                }
            } else {
                videoEl.muted = true
                videoEl.setAttribute('muted', '')
            }
            videoEl.setAttribute('autoplay', '')
            if (videoEl.volume !== targetVolume) {
                videoEl.volume = targetVolume
            }
        } catch {
            // ignore
        }
        if (!respectUserAudio) {
            setMuted(true)
        }
        setVolume(targetVolume)
        try {
            const r = videoEl.play()
            if (r && typeof r.catch === 'function') r.catch(() => { })
        } catch {
            // ignore
        }
    }, [])

    const ensureMutedAutoplayRef = useRef(ensureMutedAutoplay)
    useEffect(() => {
        ensureMutedAutoplayRef.current = ensureMutedAutoplay
    }, [ensureMutedAutoplay])

    useEffect(() => {
        setNotLive(!isLive)
        if (!isLive) {
            resetLiveVideoReady()
            try {
                hlsRef.current?.stopLoad()
            } catch {
                // ignore
            }
            void stopWhepPlaybackRef.current?.()
            playbackModeRef.current = null
            try {
                videoElRef.current?.pause()
            } catch {
                // ignore
            }
            return
        }
        setNotLive(false)
        resetLiveVideoReady()
        void startLiveStreamRef.current?.()
    }, [isLive, resetLiveVideoReady])

    useEffect(() => {
        setQualities((prev) =>
            prev.map((q) => {
                if (q.key === 'auto') return { ...q, label: qualityAutoLabel }
                if (q.key === 'level' && 'height' in q && typeof q.height === 'number') {
                    return { ...q, label: qualityHeightLabel(q.height) }
                }
                return q
            })
        )
    }, [qualityAutoLabel, qualityHeightLabel])


    const scheduleHideControls = useCallback(() => {
        if (hideControlsTimerRef.current) {
            window.clearTimeout(hideControlsTimerRef.current)
            hideControlsTimerRef.current = null
        }
        if (!isPlayingRef.current || isFullscreenRef.current || pointerInsideRef.current) return
        hideControlsTimerRef.current = window.setTimeout(() => {
            setControlsVisible(false)
        }, 2500)
    }, [])

    const showControls = useCallback(() => {
        setControlsVisible(true)
        scheduleHideControls()
    }, [scheduleHideControls])

    useEffect(() => {
        if (!mountRef.current) return
        if (videoElRef.current) return

        const mountEl = mountRef.current

        const el = document.createElement('video')
        el.setAttribute('tabindex', '-1')
        el.classList.add('focus:outline-none')
        el.style.outline = 'none'
        el.setAttribute('playsinline', 'true')
        el.setAttribute('webkit-playsinline', 'true')
        el.style.width = '100%'
        el.style.height = '100%'
        el.style.objectFit = 'contain'
        const initialMuted = userAudioUnlockedRef.current ? mutedRef.current : true
        try {
            el.muted = initialMuted
            if (initialMuted) {
                el.setAttribute('muted', '')
            }
            el.setAttribute('autoplay', '')
            el.volume = volumeRef.current > 0 ? volumeRef.current : 1
        } catch {
            // ignore
        }
        if (poster) el.setAttribute('poster', poster)
        mountEl.appendChild(el)
        videoElRef.current = el
        setMuted(initialMuted)
        setVolume(el.volume > 0 ? el.volume : 1)
        if (isLiveRef.current) {
            resetLiveVideoReady()
        }

        let hls: Hls | null = null
        let disposed = false
        const getIsLiveMode = () => isLiveRef.current

        const stopWhepPlayback = async () => {
            const handle = whepHandleRef.current
            whepHandleRef.current = null
            if (!handle) return
            try {
                await handle.stop()
            } catch {
                // ignore
            }
        }

        const markPlaybackMode = (mode: 'hls' | 'webrtc') => {
            playbackModeRef.current = mode
            if (!disposed) setPlaybackMode(mode)
        }

        const startHlsPlayback = () => {
            if (!isHlsSource(src)) {
                if (!isWhepPlaybackUrl(src)) {
                    el.src = src
                }
                return null
            }

            if (Hls.isSupported()) {
                const instance = new Hls({
                    enableWorker: true,
                    lowLatencyMode: true,
                    backBufferLength: 0,
                })
                hls = instance
                hlsRef.current = instance
                instance.loadSource(src)
                instance.attachMedia(el)
                if (!isLiveRef.current) {
                    try {
                        instance.stopLoad()
                    } catch {
                        // ignore
                    }
                }

                instance.on(Hls.Events.LEVEL_LOADED, (_, data) => {
                    if (data.details?.live) {
                        const v = videoElRef.current
                        if (v && v.buffered.length) {
                            try {
                                const behind = v.buffered.end(0) - v.currentTime
                                if (behind > 5) {
                                    v.currentTime = v.buffered.end(0) - 2
                                }
                            } catch {
                                // ignore
                            }
                        }
                    }
                })
                attachHlsErrorHandler(instance, getIsLiveMode, () => {
                    isPlayingRef.current = false
                    setIsPlaying(false)
                    setNotLive(true)
                    resetLiveVideoReady()
                })
                attachHlsQualityHandlers(instance)
                instance.on(Hls.Events.MANIFEST_PARSED, syncQualitiesFromHls)
                markPlaybackMode('hls')
                return instance
            }

            if (el.canPlayType('application/vnd.apple.mpegurl')) {
                el.src = src
                markPlaybackMode('hls')
            }
            return null
        }

        const tryStartWhepPlayback = async (): Promise<boolean> => {
            const whepUrl = resolveWhepPlaybackUrl(src)
            if (!whepUrl || disposed) return false

            try {
                await stopWhepPlayback()
                if (hlsRef.current) {
                    try {
                        hlsRef.current.destroy()
                    } catch {
                        // ignore
                    }
                    hlsRef.current = null
                    hls = null
                }
                try {
                    el.srcObject = null
                    el.removeAttribute('src')
                } catch {
                    // ignore
                }

                const handle = await startWhepPlayback({
                    videoElement: el,
                    src,
                    whepUrl,
                    onStream: () => {
                        if (disposed) return
                        tryMarkLiveVideoReadyRef.current()
                        if (isLiveRef.current) ensureMutedAutoplayRef.current()
                    },
                    onConnectionLost: () => {
                        if (disposed || !isLiveRef.current) return
                        resetLiveVideoReady()
                        scheduleReconnectLivePlaybackRef.current?.(false)
                    },
                })
                if (disposed) {
                    await handle.stop()
                    return false
                }
                whepHandleRef.current = handle
                markPlaybackMode('webrtc')
                return true
            } catch {
                await stopWhepPlayback()
                return false
            }
        }

        const startLiveStream = async () => {
            if (disposed || !isLiveRef.current) return

            try {
                hlsRef.current?.stopLoad()
            } catch {
                // ignore
            }

            const transport = playbackTransportRef.current
            const whepUrl = resolveWhepPlaybackUrl(src)
            const preferWebRtc =
                Boolean(whepUrl) &&
                transport !== 'hls' &&
                (transport === 'webrtc' || transport === 'auto')

            if (
                preferWebRtc &&
                playbackModeRef.current === 'webrtc' &&
                whepHandleRef.current
            ) {
                ensureMutedAutoplayRef.current()
                tryMarkLiveVideoReadyRef.current()
                return
            }

            if (preferWebRtc) {
                const ok = await tryStartWhepPlayback()
                if (disposed || !isLiveRef.current) return
                if (ok) {
                    ensureMutedAutoplayRef.current()
                    return
                }
                if (transport === 'webrtc') {
                    ensureMutedAutoplayRef.current()
                    return
                }
            } else if (isWhepPlaybackUrl(src)) {
                const ok = await tryStartWhepPlayback()
                if (disposed || !isLiveRef.current) return
                if (ok) {
                    ensureMutedAutoplayRef.current()
                    return
                }
            }

            if (!hlsRef.current && isHlsSource(src)) {
                hls = startHlsPlayback()
            } else {
                try {
                    hlsRef.current?.startLoad()
                } catch {
                    // ignore
                }
                if (hlsRef.current) markPlaybackMode('hls')
            }
            ensureMutedAutoplayRef.current()
        }

        const reconnectLivePlayback = async (forceFull = false) => {
            if (disposed || !isLiveRef.current || reconnectInFlightRef.current) return
            reconnectInFlightRef.current = true
            lastLiveReconnectAtRef.current = Date.now()
            resetLiveVideoReady()
            try {
                const handle = whepHandleRef.current
                if (!forceFull && playbackModeRef.current === 'webrtc' && handle) {
                    try {
                        await handle.reconnect()
                        if (disposed || !isLiveRef.current) return
                        try {
                            el.srcObject = handle.mediaStream
                        } catch {
                            // ignore
                        }
                        ensureMutedAutoplayRef.current()
                        tryMarkLiveVideoReadyRef.current()
                        return
                    } catch {
                        await stopWhepPlayback()
                    }
                } else {
                    await stopWhepPlayback()
                }
                await startLiveStream()
            } finally {
                reconnectInFlightRef.current = false
            }
        }

        const scheduleReconnectLivePlayback = (forceFull = false) => {
            if (disposed || !isLiveRef.current) return
            const minGapMs = isOwnStreamRef.current
                ? 30_000
                : forceFull
                  ? 6_000
                  : 10_000
            const elapsed = Date.now() - lastLiveReconnectAtRef.current
            if (elapsed < minGapMs) return
            void reconnectLivePlayback(forceFull)
        }

        stopWhepPlaybackRef.current = stopWhepPlayback
        startLiveStreamRef.current = startLiveStream
        reconnectLivePlaybackRef.current = reconnectLivePlayback
        scheduleReconnectLivePlaybackRef.current = scheduleReconnectLivePlayback

        void (async () => {
            if (disposed) return
            if (isLiveRef.current) {
                await startLiveStream()
            } else if (isHlsSource(src)) {
                hls = startHlsPlayback()
            }
        })()

        onReadyRef.current?.(el)
        setReady(true)
        if (isLiveRef.current) ensureMutedAutoplay()

        const onPlay = () => {
            isPlayingRef.current = true
            setIsPlaying(true)
            tryMarkLiveVideoReadyRef.current()
        }
        const onStreamFrame = () => {
            tryMarkLiveVideoReadyRef.current()
        }
        const onPause = () => {
            if (isLiveRef.current) {
                queueMicrotask(() => {
                    const video = videoElRef.current
                    if (!video || !isLiveRef.current || !video.paused) return
                    try {
                        const r = video.play()
                        if (r && typeof r.catch === 'function') r.catch(() => {})
                    } catch {
                        // ignore
                    }
                })
                return
            }
            isPlayingRef.current = false
            setIsPlaying(false)
        }
        const onTime = () => {
            try {
                const t = el.currentTime
                if (typeof t !== 'number') return

                const pending = pendingSeekToRef.current
                if (typeof pending === 'number') {
                    const delta = Math.abs(t - pending)
                    if (delta <= 0.5) {
                        pendingSeekToRef.current = null
                        setCurrentTime(t)
                    } else {
                        const now = performance.now()
                        if (now - lastSeekRetryAtRef.current > 200) {
                            try {
                                el.currentTime = pending
                            } catch {
                                // ignore
                            }
                            lastSeekRetryAtRef.current = now
                        }
                        setCurrentTime(pending)
                    }
                    return
                }

                setCurrentTime(t)
                tryMarkLiveVideoReadyRef.current()
            } catch {
                // ignore
            }
        }
        const onDuration = () => {
            try {
                const d = el.duration
                if (typeof d === 'number') setDuration(d)
            } catch {
                // ignore
            }
        }
        const onSeeked = () => {
            try {
                const t = el.currentTime
                const pending = pendingSeekToRef.current
                if (typeof pending === 'number' && typeof t === 'number') {
                    if (Math.abs(t - pending) <= 0.5) {
                        pendingSeekToRef.current = null
                        setCurrentTime(t)
                    } else {
                        setCurrentTime(pending)
                    }
                } else if (typeof t === 'number') {
                    setCurrentTime(t)
                }
            } catch {
                // ignore
            }
        }
        const onVolume = () => {
            try {
                const v = el.volume
                const m = el.muted
                if (typeof v === 'number') setVolume(v)
                if (typeof m === 'boolean') setMuted(m)
            } catch {
                // ignore
            }
        }
        const onPipEnter = () => setIsPip(true)
        const onPipLeave = () => setIsPip(false)
        const onError = () => {
            if (isLive) return
            try {
                const mediaError = (el as any).error
                if (mediaError && mediaError.code === 4) {
                    isPlayingRef.current = false
                    setIsPlaying(false)
                    setNotLive(true)
                    resetLiveVideoReady()
                }
            } catch {
                // ignore
            }
        }
        const syncLiveState = () => {
            if (!isLiveRef.current) return
            try {
                const byDuration = el.duration === Infinity || !Number.isFinite(el.duration)
                const next = Boolean(byDuration)
                if (isLiveByPlayerRef.current === next) return
                isLiveByPlayerRef.current = next
                setIsLiveByPlayer(next)
                onDetectedLiveChangeRef.current?.(next)
            } catch {
                // ignore
            }
        }
        const syncFullscreenState = () => {
            const wrapper = containerRef.current
            const video = videoElRef.current
            const fs = isVideoFullscreenActive(wrapper, video)
            isFullscreenRef.current = fs
            setIsFullscreen(fs)
            setControlsVisible(true)
            scheduleHideControls()
        }
        const onFs = () => syncFullscreenState()
        const onWebkitFsBegin = () => syncFullscreenState()
        const onWebkitFsEnd = () => syncFullscreenState()
        const liveReadyPoll = window.setInterval(() => {
            if (!isLiveRef.current) {
                window.clearInterval(liveReadyPoll)
                return
            }
            if (tryMarkLiveVideoReadyRef.current()) {
                window.clearInterval(liveReadyPoll)
            }
        }, 250)
        el.addEventListener('play', onPlay)
        el.addEventListener('pause', onPause)
        el.addEventListener('playing', onStreamFrame)
        el.addEventListener('loadeddata', onStreamFrame)
        el.addEventListener('resize', onStreamFrame)
        el.addEventListener('timeupdate', onTime)
        el.addEventListener('durationchange', onDuration)
        el.addEventListener('loadedmetadata', onDuration)
        el.addEventListener('seeked', onSeeked)
        el.addEventListener('volumechange', onVolume)
        el.addEventListener('enterpictureinpicture', onPipEnter)
        el.addEventListener('leavepictureinpicture', onPipLeave)
        el.addEventListener('loadedmetadata', syncLiveState)
        el.addEventListener('durationchange', syncLiveState)
        el.addEventListener('timeupdate', syncLiveState)
        el.addEventListener('error', onError)
        for (const ev of FULLSCREEN_CHANGE_EVENTS) {
            document.addEventListener(ev, onFs)
        }
        el.addEventListener('webkitbeginfullscreen', onWebkitFsBegin)
        el.addEventListener('webkitendfullscreen', onWebkitFsEnd)
        return () => {
            disposed = true
            startLiveStreamRef.current = null
            stopWhepPlaybackRef.current = null
            reconnectLivePlaybackRef.current = null
            scheduleReconnectLivePlaybackRef.current = null
            window.clearInterval(liveReadyPoll)
            void stopWhepPlayback()
            playbackModeRef.current = null
            try {
                el.srcObject = null
            } catch {
                // ignore
            }
            if (hlsRef.current) {
                try {
                    hlsRef.current.destroy()
                } catch {
                    // ignore
                }
                hlsRef.current = null
            }
            try {
                el.removeEventListener('play', onPlay)
                el.removeEventListener('pause', onPause)
                el.removeEventListener('playing', onStreamFrame)
                el.removeEventListener('loadeddata', onStreamFrame)
                el.removeEventListener('resize', onStreamFrame)
                el.removeEventListener('timeupdate', onTime)
                el.removeEventListener('durationchange', onDuration)
                el.removeEventListener('loadedmetadata', onDuration)
                el.removeEventListener('seeked', onSeeked)
                el.removeEventListener('volumechange', onVolume)
                el.removeEventListener('enterpictureinpicture', onPipEnter)
                el.removeEventListener('leavepictureinpicture', onPipLeave)
                el.removeEventListener('error', onError)
                el.removeEventListener('loadedmetadata', syncLiveState)
                el.removeEventListener('durationchange', syncLiveState)
                el.removeEventListener('timeupdate', syncLiveState)
            } catch {
                // ignore
            }
            for (const ev of FULLSCREEN_CHANGE_EVENTS) {
                document.removeEventListener(ev, onFs)
            }
            try {
                el.removeEventListener('webkitbeginfullscreen', onWebkitFsBegin)
                el.removeEventListener('webkitendfullscreen', onWebkitFsEnd)
            } catch {
                // ignore
            }
            try {
                if (el && mountEl?.contains(el)) {
                    mountEl.removeChild(el)
                }
            } catch {
                // ignore
            }
            videoElRef.current = null
        }
    }, [
        attachHlsQualityHandlers,
        ensureMutedAutoplay,
        poster,
        resetLiveVideoReady,
        scheduleHideControls,
        src,
        syncQualitiesFromHls,
        isLive,
    ])

    useEffect(() => {
        if (!isPlaying) {
            setControlsVisible(true)
            return
        }
        scheduleHideControls()
    }, [isPlaying, scheduleHideControls])

    useEffect(() => {
        const onDocDown = (e: MouseEvent) => {
            if (!settingsOpen) return
            const t = e.target as Node | null
            if (!t) return
            if (settingsWrapRef.current?.contains(t)) return
            setSettingsOpen(false)
            setSettingsPanel('main')
        }
        const onEsc = (e: KeyboardEvent) => {
            if (!settingsOpen) return
            if (e.key === 'Escape') {
                if (settingsPanel === 'quality') {
                    setSettingsPanel('main')
                } else {
                    setSettingsOpen(false)
                }
            }
        }
        document.addEventListener('mousedown', onDocDown)
        document.addEventListener('keydown', onEsc)
        return () => {
            document.removeEventListener('mousedown', onDocDown)
            document.removeEventListener('keydown', onEsc)
        }
    }, [settingsOpen, settingsPanel])

    const closeSettingsMenu = useCallback(() => {
        setSettingsOpen(false)
        setSettingsPanel('main')
    }, [])

    const toggleSettingsMenu = useCallback(() => {
        setSettingsOpen((open) => {
            if (open) {
                setSettingsPanel('main')
                return false
            }
            setSettingsPanel('main')
            return true
        })
        showControls()
    }, [showControls])

    const onSelectAuto = useCallback(() => {
        const hls = hlsRef.current
        if (!hls) return
        hls.currentLevel = -1
        setActive('auto')
        closeSettingsMenu()
    }, [closeSettingsMenu])

    const onSelectHeight = useCallback(
        (height: number) => {
            const hls = hlsRef.current
            if (!hls?.levels) return
            for (let i = 0; i < hls.levels.length; i++) {
                if (hls.levels[i]?.height === height) {
                    hls.currentLevel = i
                    setActive(height)
                    closeSettingsMenu()
                    return
                }
            }
        },
        [closeSettingsMenu]
    )

    const toggleMute = useCallback(() => {
        const videoEl = videoElRef.current
        if (!videoEl) return
        userAudioUnlockedRef.current = true
        const nextMuted = !videoEl.muted
        videoEl.muted = nextMuted
        mutedRef.current = nextMuted
        setMuted(nextMuted)
        if (nextMuted) {
            videoEl.setAttribute('muted', '')
        } else {
            videoEl.removeAttribute('muted')
        }
        if (!nextMuted && videoEl.volume === 0) {
            const restore = volumeRef.current > 0 ? volumeRef.current : 1
            videoEl.volume = restore
            setVolume(restore)
        }
    }, [])

    const setPlayerVolume = (v: number) => {
        const videoEl = videoElRef.current
        if (!videoEl) return
        const next = clamp(v, 0, 1)
        if (next > 0) {
            userAudioUnlockedRef.current = true
        }
        videoEl.volume = next
        setVolume(next)
        if (next > 0 && videoEl.muted) {
            videoEl.muted = false
            mutedRef.current = false
            setMuted(false)
            videoEl.removeAttribute('muted')
        }
    }

    const toggleFullscreen = useCallback(() => {
        const wrapper = containerRef.current
        const video = videoElRef.current
        if (!wrapper) return

        const isFs = isVideoFullscreenActive(wrapper, video)
        if (isFs) {
            void exitVideoFullscreen(video)
        } else {
            void requestVideoFullscreen(wrapper, video)
        }
    }, [])

    const seekBy = useCallback(
        (delta: number) => {
            const videoEl = videoElRef.current
            const range = getSeekableRange()
            if (!videoEl || !range) return

            const current = videoEl.currentTime ?? currentTime
            const target = clamp(current + delta, range.start, range.end)
            pendingSeekToRef.current = target
            lastSeekRetryAtRef.current = performance.now()
            setCurrentTime(target)
            try {
                videoEl.currentTime = target
            } catch {
                // ignore
            }
        },
        [currentTime, getSeekableRange]
    )

    const pipSupported = typeof document !== 'undefined' && 'pictureInPictureEnabled' in document && (document as any).pictureInPictureEnabled !== false

    const togglePip = () => {
        const video = videoElRef.current
        if (!pipSupported || !video) return
        if ((document as any).pictureInPictureElement === video) {
            ; (document as any).exitPictureInPicture?.().catch(() => {
                // ignore
            })
        } else {
            video.requestPictureInPicture?.().catch(() => {
                // ignore
            })
        }
    }

    const showCenterToast = useCallback((icon: 'play' | 'pause') => {
        setCenterToastIcon(icon)
        setCenterToastVisible(true)
        if (centerToastTimerRef.current !== null) {
            window.clearTimeout(centerToastTimerRef.current)
            centerToastTimerRef.current = null
        }
        centerToastTimerRef.current = window.setTimeout(() => {
            setCenterToastVisible(false)
            centerToastTimerRef.current = null
        }, 300)
    }, [])

    useEffect(() => {
        return () => {
            if (centerToastTimerRef.current !== null) {
                window.clearTimeout(centerToastTimerRef.current)
                centerToastTimerRef.current = null
            }
        }
    }, [])

    const isTypingElement = (target: EventTarget | null) => {
        const el = target as HTMLElement | null
        if (!el) return false
        const tag = el.tagName
        if (el.isContentEditable) return true
        return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || tag === 'BUTTON'
    }

    useEffect(() => {
        if (useDefaultControls || disableUserControls) return
        const onKey = (e: KeyboardEvent) => {
            if (e.metaKey || e.ctrlKey || e.altKey) return
            if (isTypingElement(e.target)) return

            const key = e.key
            const keyLower = key.toLowerCase()

            if (key === ' ' || key === 'Spacebar') {
                if (effectiveIsLive) return
                e.preventDefault()
                const videoEl = videoElRef.current
                if (!videoEl) return
                if (isPlayingRef.current) {
                    showCenterToast('pause')
                    videoEl.pause()
                } else {
                    showCenterToast('play')
                    videoEl.play().catch(() => {
                        // ignore
                    })
                }
                showControls()
                return
            }

            switch (keyLower) {
                case 'p': {
                    if (effectiveIsLive) break
                    e.preventDefault()
                    const videoEl = videoElRef.current
                    if (!videoEl) return
                    if (videoEl.paused) {
                        showCenterToast('play')
                        videoEl.play().catch(() => {
                            // ignore
                        })
                        showControls()
                    }
                    break
                }
                case 'k': {
                    if (effectiveIsLive) break
                    e.preventDefault()
                    const videoEl = videoElRef.current
                    if (!videoEl) return
                    if (videoEl.paused) {
                        showCenterToast('play')
                        videoEl.play().catch(() => {
                            // ignore
                        })
                    } else {
                        showCenterToast('pause')
                        videoEl.pause()
                    }
                    showControls()
                    break
                }
                case 'm': {
                    e.preventDefault()
                    toggleMute()
                    showControls()
                    break
                }
                case 't': {
                    if (!showTheaterToggle) break
                    e.preventDefault()
                    onToggleTheater?.()
                    showControls()
                    break
                }
                case 'f': {
                    e.preventDefault()
                    toggleFullscreen()
                    showControls()
                    break
                }
                case 'arrowleft': {
                    if (effectiveIsLive) return
                    e.preventDefault()
                    seekBy(-5)
                    showControls()
                    break
                }
                case 'arrowright': {
                    if (effectiveIsLive) return
                    e.preventDefault()
                    seekBy(5)
                    showControls()
                    break
                }
                default:
                    break
            }
        }
        window.addEventListener('keydown', onKey)
        return () => {
            window.removeEventListener('keydown', onKey)
        }
    }, [
        effectiveIsLive,
        onToggleTheater,
        seekBy,
        showControls,
        showCenterToast,
        showTheaterToggle,
        toggleFullscreen,
        toggleMute,
        useDefaultControls,
        disableUserControls,
    ])

    const commitSeekPercent = (pct: number) => {
        const videoEl = videoElRef.current
        if (!videoEl) return

        const range = getSeekableRange()
        if (!range) return

        const span = range.end - range.start
        if (!Number.isFinite(span) || span <= 0) return

        const t = clamp(range.start + (pct / 100) * span, range.start, range.end)
        pendingSeekToRef.current = t
        lastSeekRetryAtRef.current = performance.now()
        setCurrentTime(t)
        try {
            videoEl.currentTime = t
        } catch {
            // ignore
        }
    }

    const progressTrackRef = useRef<HTMLDivElement | null>(null)
    const draggingRef = useRef(false)

    const getPctFromClientX = (clientX: number) => {
        const el = progressTrackRef.current
        if (!el) return 0
        const r = el.getBoundingClientRect()
        const x = clamp(clientX - r.left, 0, r.width)
        return r.width > 0 ? (x / r.width) * 100 : 0
    }

    const volTrackRef = useRef<HTMLDivElement | null>(null)
    const volDraggingRef = useRef(false)
    const [volBarActive, setVolBarActive] = useState(false)
    const volPct = useMemo(() => clamp((muted ? 0 : volume) * 100, 0, 100), [muted, volume])

    const getVolPctFromClientX = (clientX: number) => {
        const el = volTrackRef.current
        if (!el) return 0
        const r = el.getBoundingClientRect()
        const x = clamp(clientX - r.left, 0, r.width)
        return r.width > 0 ? (x / r.width) * 100 : 0
    }

    useEffect(() => {
        setQualities((prev) =>
            prev.map((q) => {
                if (q.key === 'auto') return { ...q, label: qualityAutoLabel }
                if (q.key === 'level' && 'height' in q && typeof q.height === 'number') {
                    return { ...q, label: qualityHeightLabel(q.height) }
                }
                return q
            })
        )
    }, [qualityAutoLabel, qualityHeightLabel])

    const [theaterFollowing, setTheaterFollowing] = useState(false)
    const [theaterLiked, setTheaterLiked] = useState(false)
    const [theaterFlyingStars, setTheaterFlyingStars] = useState<number[]>([])
    const [theaterFlyingHearts, setTheaterFlyingHearts] = useState<number[]>([])

    const triggerTheaterStars = useCallback(() => {
        const now = Date.now()
        const ids = Array.from({ length: 5 }, (_, i) => now + i)
        setTheaterFlyingStars(ids)
        window.setTimeout(() => {
            setTheaterFlyingStars([])
        }, 1000)
    }, [])

    const triggerTheaterHearts = useCallback(() => {
        const now = Date.now()
        const ids = Array.from({ length: 5 }, (_, i) => now + i)
        setTheaterFlyingHearts(ids)
        window.setTimeout(() => {
            setTheaterFlyingHearts([])
        }, 1000)
    }, [])

    const refreshStreamStats = useCallback(async () => {
        const video = videoElRef.current
        const mode = playbackModeRef.current
        const statsCtx = {
            video,
            viewportEl: mountRef.current,
            sessionId: playbackSessionId,
            hls: hlsRef.current,
        }
        const apply = (base: StreamPlaybackStats) =>
            setStreamStats(enrichStreamPlaybackStats(base, statsCtx))

        if (mode === 'webrtc') {
            const pc = whepHandleRef.current?.peerConnection ?? null
            if (pc && pc.connectionState !== 'closed') {
                try {
                    const next = await collectWebRtcPlaybackStats(
                        pc,
                        streamStatsAccRef.current,
                        video
                    )
                    apply(next)
                    return
                } catch {
                    // fall through
                }
            }
        }
        if (video && (video.srcObject || video.src)) {
            if (mode === 'hls' && hlsRef.current) {
                apply(collectVideoPlaybackStats(video, 'hls', hlsRef.current))
                return
            }
            apply(collectVideoPlaybackStats(video, mode === 'hls' ? 'hls' : 'native', null))
            return
        }
        apply(emptyStreamPlaybackStats())
    }, [playbackSessionId])

    const handlePlayerContextMenu = useCallback(
        (e: ReactMouseEvent) => {
            if (useDefaultControls || !enableStreamStatsMenu || notLive) {
                e.preventDefault()
                return
            }
            e.preventDefault()
            e.stopPropagation()
            setStreamStatsOpen(false)
            setPlayerContextMenu({ x: e.clientX, y: e.clientY })
        },
        [useDefaultControls, enableStreamStatsMenu, notLive]
    )

    useEffect(() => {
        if (!notLive) return
        setPlayerContextMenu(null)
        setStreamStatsOpen(false)
    }, [notLive])

    const openStreamStatsPanel = useCallback(() => {
        if (!playerContextMenu) return
        streamStatsAccRef.current = createStreamStatsAccumulator()
        setStreamStatsOpen(true)
        setPlayerContextMenu(null)
        void refreshStreamStats()
    }, [playerContextMenu, refreshStreamStats])

    useEffect(() => {
        if (!streamStatsOpen) return
        const id = window.setInterval(() => {
            void refreshStreamStats()
        }, 500)
        return () => window.clearInterval(id)
    }, [streamStatsOpen, refreshStreamStats])

    useEffect(() => {
        if (!streamStatsOpen) return
        const bump = () => {
            void refreshStreamStats()
        }

        const video = videoElRef.current
        video?.addEventListener('resize', bump)

        const hls = hlsRef.current
        hls?.on(Hls.Events.LEVEL_SWITCHED, bump)

        return () => {
            video?.removeEventListener('resize', bump)
            hls?.off(Hls.Events.LEVEL_SWITCHED, bump)
        }
    }, [streamStatsOpen, refreshStreamStats, ready])

    return (
        <div
            ref={containerRef}
            className={cx(
                'w-full',
                className,
                isTheater ? '[@media(min-width:965px)]:h-screen [@media(min-width:965px)]:max-h-screen' : ''
            )}
        >
            <div
                className={cx(
                    'w-full overflow-hidden h-full',
                    !isTheater && '[@media(min-width:965px)]:rounded-xl'
                )}
            >
                <div
                    className={cx(
                        'relative w-full',
                        isTheater
                            ? 'aspect-video [@media(min-width:965px)]:h-full [@media(min-width:965px)]:max-h-screen [@media(min-width:965px)]:aspect-auto'
                            : fillHeight
                              ? 'min-h-full h-full w-full'
                              : 'aspect-video'
                    )}
                    onMouseMove={controlsHoverEnabled ? showControls : undefined}
                    onMouseEnter={controlsHoverEnabled ? showControls : undefined}
                    onMouseLeave={
                        controlsHoverEnabled
                            ? () => {
                                  if (dashboardPreviewEnterMode) {
                                      setControlsVisible(false)
                                      return
                                  }
                                  if (isPlaying) scheduleHideControls()
                              }
                            : undefined
                    }
                    onDoubleClick={(e) => {
                        if (useDefaultControls || notLive || disableUserControls) return
                        e.preventDefault()
                        e.stopPropagation()
                        toggleFullscreen()
                        showControls()
                    }}
                    onContextMenu={handlePlayerContextMenu}
                    onClick={
                        disableUserControls
                            ? undefined
                            : () => {
                                  if (useDefaultControls) return
                                  if (notLive) return
                                  if (effectiveIsLive) {
                                      showControls()
                                      return
                                  }
                                  showCenterToast(isPlaying ? 'pause' : 'play')
                                  togglePlay()
                                  showControls()
                              }
                    }
                >
                    <div ref={mountRef} className="absolute inset-0 w-full h-full" />

                    {showLiveStreamLoading && (
                        <div className="absolute inset-0 z-20 overflow-hidden bg-black pointer-events-none">
                            <LiveStreamLoadingMedia variant="loop" />
                        </div>
                    )}

                    {notLive && (
                        <div className="absolute inset-0 z-20 overflow-hidden bg-black select-none">
                            <LiveStreamLoadingMedia
                                variant="still"
                                className="absolute inset-0 h-full w-full object-cover pointer-events-none"
                            />
                            {showNotLiveLabel && (
                                <div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 flex justify-center bg-gradient-to-t from-black/80 via-black/40 to-transparent px-4 pb-6 pt-16">
                                    <div className="text-lg font-semibold text-[#edecec]">
                                        {t('room.notLive')}
                                    </div>
                                </div>
                            )}
                        </div>
                    )}

                    {isTheater && theaterInfo && !notLive && !disableUserControls && (
                        <div
                            className={cx(
                                'pointer-events-none absolute top-0 left-0 right-0 z-30 px-4 pt-3 pb-6',
                                'flex flex-col',
                                'bg-gradient-to-b from-black/80 via-black/40 to-transparent',
                                'transition-all duration-200 ease-out',
                                controlsVisible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2'
                            )}
                        >
                            <div
                                className="flex items-start gap-4 pointer-events-auto"
                                onClick={(e) => {
                                    e.stopPropagation()
                                }}
                            >
                                {onStreamerClick ? (
                                    <button
                                        type="button"
                                        onClick={onStreamerClick}
                                        className="relative w-12 h-12 rounded-full overflow-hidden border border-white/10 bg-white/10 flex-shrink-0 flex items-center justify-center cursor-pointer hover:opacity-80 transition-opacity"
                                        aria-label={theaterInfo.channelName}
                                    >
                                        {theaterInfo.avatar ? (
                                            <Image
                                                src={theaterInfo.avatar}
                                                alt={theaterInfo.channelName}
                                                fill
                                                className="pointer-events-none object-cover"
                                                sizes="48px"
                                                priority={false}
                                            />
                                        ) : (
                                            <FaUser className="w-5 h-5 text-white/50" aria-hidden="true" />
                                        )}
                                    </button>
                                ) : (
                                    <div className="relative w-12 h-12 rounded-full overflow-hidden border border-white/10 bg-white/10 flex-shrink-0 flex items-center justify-center">
                                        {theaterInfo.avatar ? (
                                            <Image
                                                src={theaterInfo.avatar}
                                                alt={theaterInfo.channelName}
                                                fill
                                                className="pointer-events-none object-cover"
                                                sizes="48px"
                                                priority={false}
                                            />
                                        ) : (
                                            <FaUser className="w-5 h-5 text-white/50" aria-hidden="true" />
                                        )}
                                    </div>
                                )}
                                <div className="min-w-0 flex-1 text-white">
                                    <div className="flex items-center gap-3 min-w-0">
                                        <div className="relative inline-flex items-center gap-2 min-w-0">
                                            {onStreamerClick ? (
                                                <button
                                                    type="button"
                                                    className="truncate text-left font-semibold text-base sm:text-lg cursor-pointer hover:opacity-80 transition-opacity"
                                                    onClick={onStreamerClick}
                                                >
                                                    {theaterInfo.channelName}
                                                </button>
                                            ) : (
                                                <span className="truncate text-left font-semibold text-base sm:text-lg">
                                                    {theaterInfo.channelName}
                                                </span>
                                            )}
                                        </div>
                                        <div className="flex items-center gap-2">
                                            {(onToggleLike || onToggleFollow) && (
                                                <LikeFollowActionGroup
                                                    variant="theater"
                                                    showFollow={!isOwnStream && Boolean(onToggleFollow)}
                                                    showLike={Boolean(onToggleLike)}
                                                    isFollowing={Boolean(isFollowing)}
                                                    isLiked={Boolean(isLiked)}
                                                    followCount={theaterInfo.followers ?? 0}
                                                    likeCount={likeCount ?? 0}
                                                    followTitle={
                                                        isFollowing ? t('room.unfollow') : t('room.follow')
                                                    }
                                                    likeTitle={isLiked ? t('room.unlike') : t('room.like')}
                                                    onToggleFollow={
                                                        onToggleFollow
                                                            ? () => {
                                                                  if (!isFollowing) triggerTheaterStars()
                                                                  onToggleFollow()
                                                              }
                                                            : undefined
                                                    }
                                                    onToggleLike={
                                                        onToggleLike
                                                            ? () => {
                                                                  if (!isLiked) triggerTheaterHearts()
                                                                  onToggleLike()
                                                              }
                                                            : undefined
                                                    }
                                                    flyingStars={theaterFlyingStars}
                                                    flyingHearts={theaterFlyingHearts}
                                                />
                                            )}
                                        </div>
                                    </div>
                                    <div className="mt-1 text-sm sm:text-base text-white/90 font-semibold truncate">
                                        {theaterInfo.title}
                                    </div>
                                    <div className="mt-1 flex items-center gap-3 text-sm text-white/80">
                                        <span className="inline-flex items-center gap-1">
                                            <FaUser className="w-3.5 h-3.5" />
                                            {theaterInfo.viewers.toLocaleString()}
                                        </span>
                                        {/* 直播模式：顯示「開播至今」hh:mm:ss（若 startedAt 無效則 fallback 播放時間） */}
                                        {effectiveIsLive && (
                                            <span
                                                className="text-xs text-white/70 whitespace-nowrap cursor-default"
                                                title={theaterStartedAtTitle || undefined}
                                            >
                                                {formatLiveDuration(theaterInfo.startedAt, liveNowMs) || formatTime(currentTime)}
                                            </span>
                                        )}
                                        {/* 影片模式：顯示「x 秒前 / x 分鐘前 / x 小時前 / x 天前」；無法相對時間則退回顯示日期 */}
                                        {!effectiveIsLive && theaterInfo.startedAt && (
                                            <span
                                                className="text-xs text-white/70 whitespace-nowrap cursor-default"
                                                title={theaterStartedAtTitle || undefined}
                                            >
                                                {formatLastStreamAtLabel(theaterInfo.startedAt, liveNowMs)}
                                            </span>
                                        )}
                                    </div>
                                </div>
                            </div>
                        </div>
                    )}

                    {/* 重新開啟聊天（右上角）— 跟隨 ctrl bar hover 顯示 */}
                    {showChatReopenButton && !useDefaultControls && !disableUserControls && (
                        <div
                            className={cx(
                                'absolute top-3 right-3 z-30',
                                'transition-[opacity,transform] duration-200 ease-out',
                                controlsVisible ? 'opacity-100 translate-y-0 pointer-events-auto' : 'opacity-0 -translate-y-1 pointer-events-none'
                            )}
                            onClick={(e) => e.stopPropagation()}
                        >
                            <PlayerIconButton
                                ariaLabel={chatLabel}
                                tip={chatLabel}
                                tipSide="bottom"
                                onClick={() => {
                                    onChatReopen?.()
                                    showControls()
                                }}
                                className="bg-black/30 backdrop-blur border border-white/10 hover:border-white/20"
                            >
                                <FiMessageSquare className="w-5 h-5" />
                            </PlayerIconButton>
                        </div>
                    )}

                    {dashboardPreviewOverlay && (
                        <div className="video_blind pointer-events-none absolute inset-0 z-[38] flex items-center justify-center bg-black/80">
                            {dashboardPreviewOverlay}
                        </div>
                    )}

                    {dashboardPreviewEnter && (
                        <div
                            className={cx(
                                'absolute left-1/2 top-1/2 z-[45] -translate-x-1/2 -translate-y-1/2',
                                'transition-[opacity,transform] duration-200 ease-out',
                                controlsVisible
                                    ? 'opacity-100 scale-100 pointer-events-auto'
                                    : 'opacity-0 scale-95 pointer-events-none'
                            )}
                            onClick={(e) => e.stopPropagation()}
                        >
                            <Link
                                href={dashboardPreviewEnter.href}
                                target="_blank"
                                rel="noopener noreferrer"
                                className="inline-flex items-center justify-center rounded-full bg-white px-6 py-2.5 text-sm font-semibold text-gray-900 shadow-[0_8px_24px_rgba(0,0,0,0.35)] ring-1 ring-black/5 transition-colors hover:bg-gray-50"
                            >
                                {t('user.livePreview.enter')}
                            </Link>
                        </div>
                    )}

                    {/* 中央播放 / 暫停（fadeIn / fadeOut） */}
                    {!useDefaultControls && !notLive && !disableUserControls && !effectiveIsLive && (
                        <div
                            className={cx(
                                'absolute left-1/2 top-1/2 z-25 w-20 h-20 -ml-10 -mt-10 rounded-[28px] bg-[rgba(23,25,28,0.9)]',
                                'transition-all duration-200 ease-[cubic-bezier(0.56,0.12,0.12,0.98)]',
                                centerToastVisible ? 'opacity-100 scale-100 pointer-events-auto' : 'opacity-0 scale-125 pointer-events-none'
                            )}
                            aria-hidden={!centerToastVisible}
                        >
                            <button
                                type="button"
                                className="w-full h-full inline-flex items-center justify-center"
                                onClick={(e) => {
                                    e.stopPropagation()
                                    showCenterToast(isPlaying ? 'pause' : 'play')
                                    togglePlay()
                                    showControls()
                                }}
                                aria-label={isPlaying ? t('room.player.pause') : t('room.player.play')}
                            >
                                {centerToastIcon === 'pause' ? <FaPause className="w-10 h-10 text-white" /> : <FaPlay className="w-10 h-10 text-white" />}
                            </button>
                        </div>
                    )}

                    {/* 底部控制列 */}
                    {!useDefaultControls && !notLive && !disableUserControls && (
                        <div
                            className={cx(
                                'absolute inset-x-0 bottom-0 z-50 pointer-events-none',
                                'transition-[opacity,transform] duration-200 ease-out',
                                controlsVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6'
                            )}
                            onMouseEnter={() => {
                                pointerInsideRef.current = true
                                setControlsVisible(true)
                            }}
                            onMouseLeave={() => {
                                pointerInsideRef.current = false
                                if (isPlaying) scheduleHideControls()
                            }}
                            onClick={(e) => e.stopPropagation()}
                            onMouseDown={(e) => e.stopPropagation()}
                        >
                            <div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(0,0,0,0.0001)_0.2%,rgba(0,0,0,0.0003)_0.94%,rgba(0,0,0,0.0018)_3.09%,rgba(0,0,0,0.0057)_6.5%,rgba(0,0,0,0.0134)_11.06%,rgba(0,0,0,0.0260)_16.63%,rgba(0,0,0,0.0449)_23.08%,rgba(0,0,0,0.0712)_30.28%,rgba(0,0,0,0.1063)_38.1%,rgba(0,0,0,0.1513)_46.41%,rgba(0,0,0,0.2075)_55.07%,rgba(0,0,0,0.2761)_63.97%,rgba(0,0,0,0.3584)_72.97%,rgba(0,0,0,0.4557)_81.93%,rgba(0,0,0,0.5691)_90.73%,rgba(0,0,0,0.7)_99.24%)]" />

                            <div className="relative flex flex-col justify-end h-[160px] max-h-[160px] min-h-[160px] px-5 pt-2 pb-2 pointer-events-none drop-shadow-[0_1px_3px_rgba(0,0,0,0.3)]">
                                {!effectiveIsLive && (
                                    <div className="pointer-events-auto">
                                        <div
                                            ref={progressTrackRef}
                                            className="group relative pt-2 pb-2 cursor-pointer mb-1"
                                            onClick={(e) => {
                                                e.stopPropagation()
                                                if (draggingRef.current) return
                                                const pct = getPctFromClientX(e.clientX)
                                                commitSeekPercent(pct)
                                                setSeekingPercent(null)
                                                showControls()
                                            }}
                                            onPointerDown={(e) => {
                                                if (e.button !== 0) return
                                                e.stopPropagation()
                                                draggingRef.current = true
                                                    ; (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId)
                                                const pct = getPctFromClientX(e.clientX)
                                                setSeekingPercent(pct)
                                                showControls()
                                            }}
                                            onPointerMove={(e) => {
                                                if (!draggingRef.current) return
                                                e.stopPropagation()
                                                const pct = getPctFromClientX(e.clientX)
                                                setSeekingPercent(pct)
                                            }}
                                            onPointerUp={(e) => {
                                                if (!draggingRef.current) return
                                                e.stopPropagation()
                                                draggingRef.current = false
                                                const pct = getPctFromClientX(e.clientX)
                                                commitSeekPercent(pct)
                                                setSeekingPercent(null)
                                                showControls()
                                            }}
                                            onPointerCancel={() => {
                                                draggingRef.current = false
                                                setSeekingPercent(null)
                                            }}
                                            onMouseDown={(e) => e.stopPropagation()}
                                        >
                                            <div className="relative h-[3px] group-hover:h-[5px] transition-[height] duration-150 bg-white/20">
                                                <div
                                                    className="absolute inset-y-0 left-0 bg-[#6fb9ff]"
                                                    style={{ width: `${progressValue}%` }}
                                                />
                                                <div className="absolute inset-y-0 left-0 right-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity bg-white/15" />
                                                <button
                                                    type="button"
                                                    tabIndex={-1}
                                                    className="absolute top-1/2 -translate-y-1/2 -ml-[6.5px] w-[13px] h-[13px] opacity-0 group-hover:opacity-100 transition-opacity"
                                                    style={{ left: `${progressValue}%` }}
                                                    aria-hidden="true"
                                                >
                                                    <span className="block w-[13px] h-[13px] rounded-full bg-[#6fb9ff] shadow-[2px_2px_3px_rgba(0,0,0,0.3)]" />
                                                </button>
                                            </div>

                                        </div>
                                    </div>
                                )}

                                <div className="pointer-events-auto flex items-center gap-3">
                                    <div className="flex items-center gap-1 min-w-0">
                                        {!effectiveIsLive && (
                                            <PlayerIconButton
                                                ariaLabel={isPlaying ? t('room.player.pause') : t('room.player.play')}
                                                tip={isPlaying ? t('room.player.pause') : t('room.player.play')}
                                                onClick={() => {
                                                    showCenterToast(isPlaying ? 'pause' : 'play')
                                                    togglePlay()
                                                    showControls()
                                                }}
                                            >
                                                {isPlaying ? <FaPause className="w-4 h-4" /> : <FaPlay className="w-4 h-4" />}
                                            </PlayerIconButton>
                                        )}

                                        <div
                                            className="group/vol flex items-center"
                                            onClick={(e) => e.stopPropagation()}
                                        >
                                            <PlayerIconButton
                                                ariaLabel={muted || volume === 0 ? t('room.player.unmute') : t('room.player.mute')}
                                                tip={muted || volume === 0 ? t('room.player.unmuteHotkey') : t('room.player.muteHotkey')}
                                                onClick={() => {
                                                    toggleMute()
                                                    showControls()
                                                }}
                                            >
                                                {muted || volume === 0 ? (
                                                    <FiVolumeX className="w-5 h-5" />
                                                ) : (
                                                    <FiVolume2 className="w-5 h-5" />
                                                )}
                                            </PlayerIconButton>

                                            <div className="overflow-hidden ml-1">
                                                <div
                                                    className={cx(
                                                        'transition-[width,opacity] duration-150',
                                                        'w-0 opacity-0',
                                                        (volBarActive ? 'w-20 opacity-100' : 'group-hover/vol:w-14 group-hover/vol:opacity-100')
                                                    )}
                                                >
                                                    <div
                                                        ref={volTrackRef}
                                                        className="relative h-[3px] w-14 mt-[6px] mb-[6px] rounded-full bg-white/20 cursor-pointer"
                                                        onPointerDown={(e) => {
                                                            if (e.button !== 0) return
                                                            e.stopPropagation()
                                                            volDraggingRef.current = true
                                                            setVolBarActive(true)
                                                                ; (e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId)
                                                            const pct = getVolPctFromClientX(e.clientX)
                                                            setPlayerVolume(pct / 100)
                                                            showControls()
                                                        }}
                                                        onPointerMove={(e) => {
                                                            if (!volDraggingRef.current) return
                                                            e.stopPropagation()
                                                            const pct = getVolPctFromClientX(e.clientX)
                                                            setPlayerVolume(pct / 100)
                                                        }}
                                                        onPointerUp={(e) => {
                                                            if (!volDraggingRef.current) return
                                                            e.stopPropagation()
                                                            volDraggingRef.current = false
                                                            const pct = getVolPctFromClientX(e.clientX)
                                                            setPlayerVolume(pct / 100)
                                                            window.setTimeout(() => setVolBarActive(false), 200)
                                                        }}
                                                        onPointerCancel={() => {
                                                            volDraggingRef.current = false
                                                            setVolBarActive(false)
                                                        }}
                                                        role="slider"
                                                        aria-label={t('room.player.volume')}
                                                        aria-valuemin={0}
                                                        aria-valuemax={100}
                                                        aria-valuenow={Math.round(volPct)}
                                                        tabIndex={0}
                                                        onKeyDown={(e) => {
                                                            if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
                                                                e.preventDefault()
                                                                setPlayerVolume(Math.max(0, (muted ? 0 : volume) - 0.05))
                                                            } else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
                                                                e.preventDefault()
                                                                setPlayerVolume(Math.min(1, (muted ? 0 : volume) + 0.05))
                                                            }
                                                        }}
                                                    >
                                                        <div
                                                            className="absolute inset-y-0 left-0 rounded-full bg-white"
                                                            style={{ width: `${volPct}%` }}
                                                        />
                                                        <button
                                                            type="button"
                                                            tabIndex={-1}
                                                            aria-hidden="true"
                                                            className="absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-[9px] h-[9px] rounded-full bg-white"
                                                            style={{
                                                                left: `clamp(4.5px, ${volPct}%, calc(100% - 4.5px))`,
                                                            }}
                                                        />
                                                    </div>
                                                </div>
                                            </div>
                                        </div>

                                        {!effectiveIsLive && (
                                            <>
                                                <div className="ml-2 hidden sm:flex items-center text-xs font-semibold text-white/80 min-w-0 select-none">
                                                    <span className="text-[#6fb9ff]">{formatTime(currentTime)}</span>
                                                    <span className="px-1.5 text-white/45">/</span>
                                                    <span className="text-white/70">{formatTime(duration)}</span>
                                                </div>
                                                <div
                                                    className={cx(
                                                        'ml-2 flex sm:hidden items-center text-xs font-semibold text-white/80 min-w-0 transition-opacity duration-150 select-none',
                                                        volBarActive ? 'opacity-0 pointer-events-none' : 'opacity-100'
                                                    )}
                                                >
                                                    <span className="text-[#6fb9ff]">{formatTime(currentTime)}</span>
                                                    <span className="px-1.5 text-white/45">/</span>
                                                    <span className="text-white/70">{formatTime(duration)}</span>
                                                </div>
                                            </>
                                        )}

                                        {effectiveIsLive && (
                                            <button
                                                type="button"
                                                className="ml-1 h-9 px-3 rounded-lg bg-white/0 text-white/90 font-semibold text-sm cursor-default"
                                                onClick={(e) => e.stopPropagation()}
                                                aria-label={t('room.player.live')}
                                            >
                                                <span className="inline-flex items-center gap-2">
                                                    <span className="inline-block w-2 h-2 rounded-full bg-[#ff1e1e]" />
                                                    {t('room.player.live')}
                                                </span>
                                            </button>
                                        )}
                                    </div>

                                    <div className="ml-auto flex items-center gap-2">
                                        {hlsPlayback &&
                                            playbackMode === 'hls' &&
                                            !disableUserControls &&
                                            showQualityMenu && (
                                            <div className="relative" ref={settingsWrapRef}>
                                                <PlayerIconButton
                                                    ariaLabel={t('room.player.settings')}
                                                    tip={t('room.player.settings')}
                                                    onClick={toggleSettingsMenu}
                                                    className={settingsOpen ? 'text-white' : undefined}
                                                >
                                                    <FiSettings className="w-5 h-5" />
                                                </PlayerIconButton>

                                                {settingsOpen && (
                                                    <div
                                                        className="absolute bottom-full right-0 z-[80] mb-2 min-w-[220px] overflow-hidden rounded-xl border border-white/10 bg-[#1f1f23]/95 py-1 shadow-2xl backdrop-blur-md"
                                                        role="menu"
                                                        onClick={(e) => e.stopPropagation()}
                                                    >
                                                        {settingsPanel === 'main' ? (
                                                            <button
                                                                type="button"
                                                                role="menuitem"
                                                                className="flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm font-medium text-white/90 transition-colors hover:bg-white/10"
                                                                onClick={() => setSettingsPanel('quality')}
                                                            >
                                                                <span className="flex-1">{qualityLabel}</span>
                                                                <span className="max-w-[88px] truncate text-xs text-white/55">
                                                                    {activeQualityLabel}
                                                                </span>
                                                                <FiChevronRight className="h-4 w-4 shrink-0 text-white/45" />
                                                            </button>
                                                        ) : (
                                                            <>
                                                                <button
                                                                    type="button"
                                                                    className="flex w-full items-center gap-2 border-b border-white/10 px-3 py-2.5 text-left text-sm font-semibold text-white/90 transition-colors hover:bg-white/10"
                                                                    onClick={() => setSettingsPanel('main')}
                                                                >
                                                                    <FiChevronLeft className="h-4 w-4 shrink-0 text-white/55" />
                                                                    <span>{qualityLabel}</span>
                                                                </button>
                                                                <div className="max-h-[min(240px,40vh)] overflow-y-auto py-0.5">
                                                                    {qualities.map((q) => {
                                                                        const selected =
                                                                            q.key === 'auto'
                                                                                ? active === 'auto'
                                                                                : q.key === 'level' &&
                                                                                  active === q.height
                                                                        return (
                                                                            <button
                                                                                key={
                                                                                    q.key === 'auto'
                                                                                        ? 'auto'
                                                                                        : `level-${q.height}`
                                                                                }
                                                                                type="button"
                                                                                role="menuitemradio"
                                                                                aria-checked={selected}
                                                                                className={cx(
                                                                                    'flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm transition-colors',
                                                                                    selected
                                                                                        ? 'bg-white/12 text-white font-semibold'
                                                                                        : 'text-white/85 hover:bg-white/10'
                                                                                )}
                                                                                onClick={() => {
                                                                                    if (q.key === 'auto') onSelectAuto()
                                                                                    else onSelectHeight(q.height)
                                                                                }}
                                                                            >
                                                                                <span>{q.label}</span>
                                                                                {selected ? (
                                                                                    <FiCheck className="h-4 w-4 shrink-0 text-[#6fb9ff]" />
                                                                                ) : (
                                                                                    <span className="w-4" />
                                                                                )}
                                                                            </button>
                                                                        )
                                                                    })}
                                                                </div>
                                                            </>
                                                        )}
                                                    </div>
                                                )}
                                            </div>
                                        )}

                                        <PlayerIconButton
                                            ariaLabel={isPip ? t('room.player.exitPip') : t('room.player.enterPip')}
                                            tip={isPip ? t('room.player.pipOff') : t('room.player.pipOn')}
                                            onClick={() => {
                                                togglePip()
                                                showControls()
                                            }}
                                            disabled={!pipSupported}
                                        >
                                            <MdPictureInPictureAlt className="w-5 h-5" />
                                        </PlayerIconButton>

                                        {showTheaterToggle && (
                                            <PlayerIconButton
                                                ariaLabel={isTheater ? t('room.player.exitTheater') : t('room.player.enterTheater')}
                                                tip={isTheater ? t('room.player.exitTheater') : t('room.player.enterTheater')}
                                                onClick={() => {
                                                    onToggleTheater?.()
                                                    showControls()
                                                }}
                                            >
                                                {isTheater ? <BsTvFill className="w-5 h-5" /> : <BsTv className="w-5 h-5" />}
                                            </PlayerIconButton>
                                        )}

                                        <PlayerIconButton
                                            ariaLabel={isFullscreen ? t('room.player.exitFullscreen') : t('room.player.fullscreen')}
                                            tip={isFullscreen ? t('room.player.exitFullscreen') : t('room.player.fullscreen')}
                                            onClick={() => {
                                                toggleFullscreen()
                                                showControls()
                                            }}
                                        >
                                            {isFullscreen ? (
                                                <FiMinimize className="w-5 h-5" />
                                            ) : (
                                                <FiMaximize className="w-5 h-5" />
                                            )}
                                        </PlayerIconButton>
                                    </div>
                                </div>
                            </div>
                        </div>
                    )}

                    {streamStatsOpen && (
                        <PlayerStreamStatsMenu
                            stats={streamStats}
                            onClose={() => setStreamStatsOpen(false)}
                        />
                    )}
                </div>
            </div>

            {playerContextMenu && !notLive && (
                <PlayerContextMenu
                    x={playerContextMenu.x}
                    y={playerContextMenu.y}
                    onClose={() => setPlayerContextMenu(null)}
                    onOpenStats={openStreamStatsPanel}
                />
            )}
        </div>
    )
}


