'use client'

import { useCallback, useEffect, useRef, useState } from 'react'
import Hls from 'hls.js'
import { resolveWhepPlaybackUrl } from '@/lib/streams/cloudflareStreamUrls'
import { safeVideoPlay } from '@/lib/media/safeVideoPlay'
import { startWhepPlayback, type WhepPlaybackHandle } from '@/lib/streams/whepPlayback'
import LiveStreamLoadingMedia from '@/components/LiveStreamLoadingMedia'

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

type Props = {
    /** stream_token、HLS URL 或一般影片 URL */
    src: string
    poster?: string
    className?: string
    /** false 時停止拉流（主播後台未開播） */
    isLive?: boolean
    onSourceError?: () => void
    /** 擷取到可顯示畫面後回傳靜態圖（用於縮圖，擷取後會停止拉流） */
    onSnapshot?: (dataUrl: string) => void
    /** 縮圖擷取模式：不重連、避免與其他預覽搶佔連線 */
    snapshotMode?: boolean
}

function captureVideoFrameDataUrl(video: HTMLVideoElement, maxWidth = 480): string | null {
    try {
        const vw = video.videoWidth
        const vh = video.videoHeight
        if (vw <= 0 || vh <= 0) return null
        const scale = vw > maxWidth ? maxWidth / vw : 1
        const w = Math.max(1, Math.round(vw * scale))
        const h = Math.max(1, Math.round(vh * scale))
        const canvas = document.createElement('canvas')
        canvas.width = w
        canvas.height = h
        const ctx = canvas.getContext('2d')
        if (!ctx) return null
        ctx.drawImage(video, 0, 0, w, h)
        return canvas.toDataURL('image/jpeg', 0.85)
    } catch {
        return null
    }
}

function isSourceLoadError(data: {
    fatal?: boolean
    details?: string
    type?: string
    response?: { code?: number }
}): boolean {
    if (data.fatal) return true
    const details = data.details ?? ''
    if (details === 'manifestLoadError' || details === 'manifestLoadTimeOut' || details === 'manifestParsingError') {
        return true
    }
    const code = data.response?.code
    if (typeof code === 'number' && code >= 400 && code < 500) return true
    return false
}

export default function LivePreviewPlayer({
    src,
    poster,
    className,
    isLive = true,
    onSourceError,
    onSnapshot,
    snapshotMode = false,
}: Props) {
    const mountRef = useRef<HTMLDivElement>(null)
    const loadingVideoRef = useRef<HTMLVideoElement | null>(null)
    const videoElRef = useRef<HTMLVideoElement | null>(null)
    const hlsRef = useRef<Hls | null>(null)
    const whepRef = useRef<WhepPlaybackHandle | null>(null)
    const isLiveRef = useRef(isLive)
    const onSourceErrorRef = useRef(onSourceError)
    const onSnapshotRef = useRef(onSnapshot)
    const snapshotDoneRef = useRef(false)
    const liveVideoReadyRef = useRef(false)
    const [liveVideoReady, setLiveVideoReady] = useState(false)
    onSourceErrorRef.current = onSourceError
    onSnapshotRef.current = onSnapshot
    isLiveRef.current = isLive
    const snapshotCaptureRef = useRef(snapshotMode || Boolean(onSnapshot))
    snapshotCaptureRef.current = snapshotMode || Boolean(onSnapshot)

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

    const stopPlayback = useCallback(async () => {
        if (hlsRef.current) {
            try {
                hlsRef.current.destroy()
            } catch {
                // ignore
            }
            hlsRef.current = null
        }
        const whep = whepRef.current
        whepRef.current = null
        if (whep) {
            try {
                await whep.stop()
            } catch {
                // ignore
            }
        }
        const el = videoElRef.current
        if (el) {
            try {
                el.pause()
                el.srcObject = null
                el.removeAttribute('src')
            } catch {
                // ignore
            }
        }
    }, [])

    const tryCaptureSnapshot = useCallback(async () => {
        if (!onSnapshotRef.current || snapshotDoneRef.current) return false
        const el = videoElRef.current
        if (!el) return false
        const dataUrl = captureVideoFrameDataUrl(el)
        if (!dataUrl) return false
        snapshotDoneRef.current = true
        onSnapshotRef.current(dataUrl)
        await stopPlayback()
        return true
    }, [stopPlayback])

    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)
        if (onSnapshotRef.current) {
            void tryCaptureSnapshot()
        }
        return true
    }, [tryCaptureSnapshot])

    const startPlayback = useCallback(
        async (tokenOrUrl: string) => {
            const el = videoElRef.current
            if (!el || !tokenOrUrl.trim()) return

            resetLiveVideoReady()
            await stopPlayback()

            const whepUrl = resolveWhepPlaybackUrl(tokenOrUrl)
            if (whepUrl) {
                try {
                    const handle = await startWhepPlayback({
                        videoElement: el,
                        src: tokenOrUrl,
                        whepUrl,
                        onStream: () => {
                            tryMarkLiveVideoReady()
                            try {
                                el.muted = true
                                safeVideoPlay(el)
                            } catch {
                                // ignore
                            }
                        },
                        onFailed: () => {
                            onSourceErrorRef.current?.()
                        },
                        onConnectionLost: () => {
                            if (!isLiveRef.current || snapshotCaptureRef.current) return
                            void (async () => {
                                const active = whepRef.current
                                if (!active) return
                                resetLiveVideoReady()
                                try {
                                    await active.reconnect()
                                    tryMarkLiveVideoReady()
                                    try {
                                        el.muted = true
                                        safeVideoPlay(el)
                                    } catch {
                                        // ignore
                                    }
                                } catch {
                                    await startPlayback(tokenOrUrl)
                                }
                            })()
                        },
                    })
                    whepRef.current = handle
                    return
                } catch {
                    onSourceErrorRef.current?.()
                    return
                }
            }

            if (isHlsSource(tokenOrUrl)) {
                if (Hls.isSupported()) {
                    const hls = new Hls({
                        enableWorker: true,
                        lowLatencyMode: true,
                        backBufferLength: 0,
                    })
                    hlsRef.current = hls
                    hls.on(Hls.Events.ERROR, (_event, data) => {
                        if (isSourceLoadError(data)) {
                            onSourceErrorRef.current?.()
                        }
                    })
                    hls.loadSource(tokenOrUrl)
                    hls.attachMedia(el)
                    hls.on(Hls.Events.MANIFEST_PARSED, () => {
                        tryMarkLiveVideoReady()
                        try {
                            safeVideoPlay(el)
                        } catch {
                            // ignore
                        }
                    })
                } else if (el.canPlayType('application/vnd.apple.mpegurl')) {
                    el.src = tokenOrUrl
                    try {
                        safeVideoPlay(el)
                    } catch {
                        // ignore
                    }
                }
                return
            }

            el.src = tokenOrUrl
            try {
                safeVideoPlay(el)
            } catch {
                // ignore
            }
        },
        [resetLiveVideoReady, stopPlayback, tryMarkLiveVideoReady]
    )

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

    useEffect(() => {
        if (!mountRef.current) return
        if (videoElRef.current) return
        const mountEl = mountRef.current
        const el = document.createElement('video')
        el.setAttribute('tabindex', '-1')
        el.setAttribute('playsinline', 'true')
        el.muted = true
        el.autoplay = true
        el.preload = 'auto'
        el.style.pointerEvents = 'none'
        el.style.width = '100%'
        el.style.height = '100%'
        el.style.objectFit = 'cover'
        if (poster) el.setAttribute('poster', poster)
        mountEl.appendChild(el)
        videoElRef.current = el

        const handleVideoError = () => {
            try {
                const mediaError = (el as HTMLVideoElement & { error?: MediaError }).error
                if (mediaError?.code === 4) {
                    onSourceErrorRef.current?.()
                }
            } catch {
                // ignore
            }
        }
        const onStreamFrame = () => {
            tryMarkLiveVideoReady()
        }
        el.addEventListener('error', handleVideoError)
        el.addEventListener('playing', onStreamFrame)
        el.addEventListener('loadeddata', onStreamFrame)
        el.addEventListener('resize', onStreamFrame)
        el.addEventListener('timeupdate', onStreamFrame)

        const liveReadyPoll = window.setInterval(() => {
            if (!isLiveRef.current) {
                window.clearInterval(liveReadyPoll)
                return
            }
            if (tryMarkLiveVideoReady()) {
                window.clearInterval(liveReadyPoll)
            }
        }, 250)

        return () => {
            window.clearInterval(liveReadyPoll)
            el.removeEventListener('error', handleVideoError)
            el.removeEventListener('playing', onStreamFrame)
            el.removeEventListener('loadeddata', onStreamFrame)
            el.removeEventListener('resize', onStreamFrame)
            el.removeEventListener('timeupdate', onStreamFrame)
            void stopPlayback()
            try {
                if (videoElRef.current && mountEl?.contains(videoElRef.current)) {
                    mountEl.removeChild(videoElRef.current)
                }
            } catch {
                // ignore
            }
            videoElRef.current = null
        }
    }, [poster, stopPlayback, tryMarkLiveVideoReady])

    useEffect(() => {
        if (!isLive || !src.trim()) {
            resetLiveVideoReady()
            void stopPlayback()
            return
        }
        void startPlayback(src)
    }, [isLive, src, resetLiveVideoReady, startPlayback, stopPlayback])

    const showLiveStreamLoading = isLive && !liveVideoReady && !onSnapshot

    useEffect(() => {
        const loadingEl = loadingVideoRef.current
        if (!showLiveStreamLoading || !loadingEl) return
        safeVideoPlay(loadingEl)
        return () => {
            try {
                loadingEl.pause()
            } catch {
                // ignore
            }
        }
    }, [showLiveStreamLoading])

    useEffect(() => {
        if (!isLive || liveVideoReady) return
        const intervalId = window.setInterval(() => {
            tryMarkLiveVideoReady()
        }, 250)
        return () => window.clearInterval(intervalId)
    }, [isLive, liveVideoReady, tryMarkLiveVideoReady])

    return (
        <div className={`relative h-full w-full ${className ?? ''}`}>
            <div ref={mountRef} className="absolute inset-0 h-full w-full" />
            {showLiveStreamLoading && (
                <div className="pointer-events-none absolute inset-0 z-20 overflow-hidden bg-black">
                    <LiveStreamLoadingMedia ref={loadingVideoRef} variant="loop" />
                </div>
            )}
        </div>
    )
}
