'use client'

import {
    createContext,
    useCallback,
    useContext,
    useEffect,
    useMemo,
    useRef,
    useState,
    type ReactNode,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
    buildPopularLiveStreams,
    type StreamCatalogEntry,
    type PopularStreamItem,
} from '@/lib/streams/popularStreams'
import { parseStreamStatusPayload } from '@/lib/streams/parseStreamStatusPayload'
import {
    buildStreamsWsUrl,
    getStreamsCatalogRooms,
    getStreamsSnapshotList,
    isStreamViewersUpdatePayload,
    parseStreamsWsMessage,
    sendStreamsSnapshotRequest,
    sendStreamsSubscribeAll,
    type StreamStatusPayload,
    type StreamsCatalogPayload,
} from '@/lib/streams/wsStreams'

type StreamsWsContextValue = {
    ready: boolean
    popularLiveStreams: (limit: number) => PopularStreamItem[]
    refresh: () => void
    getRoomStreamStatus: (roomId: string) => StreamStatusPayload | undefined
}

const StreamsWsContext = createContext<StreamsWsContextValue | undefined>(undefined)

function mergeCatalog(
    prev: Map<string, StreamCatalogEntry>,
    rooms: StreamsCatalogPayload['rooms']
): Map<string, StreamCatalogEntry> {
    const next = new Map(prev)
    for (const room of rooms) {
        const roomId = String(room.roomId ?? '').trim()
        if (!roomId) continue
        next.set(roomId, {
            roomId,
            publicId: String(room.publicId ?? '').trim() || roomId,
            streamerName: String(room.streamerName ?? '').trim() || roomId,
            title: String(room.title ?? '').trim(),
            category: room.category != null ? String(room.category) : null,
            thumbnail: room.thumbnail,
            src: room.src,
            avatar: room.avatar,
            likeCount: Number(room.likeCount ?? 0) || 0,
        })
    }
    return next
}

export function StreamsWsProvider({ children }: { children: ReactNode }) {
    const { t } = useTranslation()
    const wsRef = useRef<WebSocket | null>(null)
    const reconnectAttemptsRef = useRef(0)
    const reconnectTimeoutRef = useRef<number | null>(null)
    const cancelledRef = useRef(false)

    const [ready, setReady] = useState(false)
    const [catalog, setCatalog] = useState<Map<string, StreamCatalogEntry>>(() => new Map())
    const [statusByRoomId, setStatusByRoomId] = useState<Map<string, StreamStatusPayload>>(
        () => new Map()
    )
    const [viewersByRoomId, setViewersByRoomId] = useState<Map<string, number>>(() => new Map())

    const applyStatus = useCallback((payload: StreamStatusPayload | null | undefined) => {
        const roomId = String(payload?.roomId ?? '').trim()
        if (!roomId || !payload) return
        setStatusByRoomId((prev) => {
            const next = new Map(prev)
            next.set(roomId, payload)
            return next
        })
    }, [])

    const applySnapshot = useCallback((streams: StreamStatusPayload[] | undefined) => {
        if (!streams?.length) return
        setStatusByRoomId((prev) => {
            const next = new Map(prev)
            for (const row of streams) {
                const roomId = String(row.roomId ?? '').trim()
                if (roomId) next.set(roomId, row)
            }
            return next
        })
    }, [])

    const connect = useCallback(() => {
        if (cancelledRef.current || typeof window === 'undefined') return

        try {
            const ws = new WebSocket(buildStreamsWsUrl())
            wsRef.current = ws

            ws.onopen = () => {
                reconnectAttemptsRef.current = 0
                sendStreamsSubscribeAll(ws)
            }

            ws.onmessage = (event) => {
                try {
                    const data = parseStreamsWsMessage(event.data as string)
                    if (!data) return
                    if (data.type === 'streams_catalog') {
                        const rooms = getStreamsCatalogRooms(data.data)
                        if (rooms?.length) {
                            setCatalog((prev) => mergeCatalog(prev, rooms))
                            setReady(true)
                        }
                        return
                    }
                    if (data.type === 'streams_snapshot') {
                        applySnapshot(getStreamsSnapshotList(data.data))
                        setReady(true)
                        return
                    }
                    if (data.type === 'stream_status') {
                        applyStatus(parseStreamStatusPayload(data.data))
                        return
                    }
                    if (data.type === 'viewers_update' && isStreamViewersUpdatePayload(data.data)) {
                        const payload = data.data
                        const roomId = String(payload.roomId ?? '').trim()
                        if (!roomId) return
                        setViewersByRoomId((prev) => {
                            const next = new Map(prev)
                            next.set(roomId, Math.max(0, Math.floor(payload.viewers)))
                            return next
                        })
                    }
                } catch {
                    // ignore malformed frames
                }
            }

            ws.onclose = () => {
                wsRef.current = null
                setReady(false)
                if (cancelledRef.current || reconnectAttemptsRef.current >= 5) return
                reconnectAttemptsRef.current++
                const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 10000)
                reconnectTimeoutRef.current = window.setTimeout(() => {
                    if (!cancelledRef.current) connect()
                }, delay)
            }
        } catch {
            // ignore
        }
    }, [applySnapshot, applyStatus])

    useEffect(() => {
        cancelledRef.current = false
        connect()

        return () => {
            cancelledRef.current = true
            if (reconnectTimeoutRef.current != null) {
                window.clearTimeout(reconnectTimeoutRef.current)
                reconnectTimeoutRef.current = null
            }
            const ws = wsRef.current
            wsRef.current = null
            if (ws) {
                ws.onclose = null
                ws.onmessage = null
                ws.onerror = null
                ws.close()
            }
        }
    }, [connect])

    const defaultCategoryLabel = t('sidebar.popular')

    const popularLiveStreams = useCallback(
        (limit: number) => {
            try {
                return buildPopularLiveStreams(
                    [...catalog.values()],
                    statusByRoomId,
                    viewersByRoomId,
                    limit,
                    defaultCategoryLabel
                )
            } catch (error) {
                console.error('[StreamsWs] buildPopularLiveStreams failed:', error)
                return []
            }
        },
        [catalog, statusByRoomId, viewersByRoomId, defaultCategoryLabel]
    )

    const refresh = useCallback(() => {
        const ws = wsRef.current
        if (ws?.readyState === WebSocket.OPEN) {
            sendStreamsSnapshotRequest(ws)
        }
    }, [])

    const getRoomStreamStatus = useCallback(
        (roomId: string) => {
            const id = String(roomId ?? '').trim()
            if (!id) return undefined
            return statusByRoomId.get(id)
        },
        [statusByRoomId]
    )

    const value = useMemo(
        () => ({
            ready,
            popularLiveStreams,
            refresh,
            getRoomStreamStatus,
        }),
        [ready, popularLiveStreams, refresh, getRoomStreamStatus]
    )

    return <StreamsWsContext.Provider value={value}>{children}</StreamsWsContext.Provider>
}

export function useStreamsWs() {
    const ctx = useContext(StreamsWsContext)
    if (!ctx) {
        throw new Error('useStreamsWs must be used within StreamsWsProvider')
    }
    return ctx
}

export function usePopularLiveStreams(limit: number) {
    const { ready, popularLiveStreams, refresh } = useStreamsWs()
    const streams = useMemo(() => popularLiveStreams(limit), [popularLiveStreams, limit])
    return { streams, ready, refresh }
}

/** 嵌入播放器：讀取全域 /streams WS 的房間直播狀態 */
export function useRoomStreamStatus(roomId: string): StreamStatusPayload | undefined {
    const { getRoomStreamStatus } = useStreamsWs()
    const id = String(roomId ?? '').trim()
    return useMemo(() => (id ? getRoomStreamStatus(id) : undefined), [getRoomStreamStatus, id])
}
