'use client'

import React, { createContext, useContext, useCallback, useEffect, useRef, useState } from 'react'
import type { Track } from '@/components/music/music-provider'
import {
  saveTrack,
  removeTrack as removeFromDB,
  listOfflineIds,
  getTotalSize,
  clearAll as clearDB,
  listOfflineTracks,
} from '@/lib/offline-db'

type DownloadStatus = 'idle' | 'queued' | 'downloading'

interface OfflineContextValue {
  offlineIds: Set<string>
  queue: Track[]
  activeDownloadId: string | null
  storageUsed: number
  download: (track: Track) => void
  remove: (trackId: string) => void
  clearAll: () => void
  getStatus: (trackId: string) => DownloadStatus
  sync: () => Promise<void>
  offlineTracks: Track[]
  loading: boolean
}

const OfflineContext = createContext<OfflineContextValue | null>(null)

export function useOffline() {
  const ctx = useContext(OfflineContext)
  if (!ctx) throw new Error('useOffline must be used within OfflineProvider')
  return ctx
}

export function OfflineProvider({ children }: { children: React.ReactNode }) {
  const [offlineIds, setOfflineIds] = useState<Set<string>>(new Set())
  const [offlineTracks, setOfflineTracks] = useState<Track[]>([])
  const [queue, setQueue] = useState<Track[]>([])
  const [activeDownloadId, setActiveDownloadId] = useState<string | null>(null)
  const [storageUsed, setStorageUsed] = useState(0)
  const [loading, setLoading] = useState(true)
  const processingRef = useRef(false)
  // Mirror queue in a ref for access in async loops
  const queueRef = useRef<Track[]>([])

  const refreshLocal = useCallback(async () => {
    const [ids, tracks, size] = await Promise.all([
      listOfflineIds(),
      listOfflineTracks(),
      getTotalSize(),
    ])
    setOfflineIds(new Set(ids))
    setOfflineTracks(tracks)
    setStorageUsed(size)
  }, [])

  const updateQueue = useCallback((updater: (prev: Track[]) => Track[]) => {
    setQueue((prev) => {
      const next = updater(prev)
      queueRef.current = next
      return next
    })
  }, [])

  const processQueue = useCallback(async () => {
    if (processingRef.current) return
    processingRef.current = true

    while (queueRef.current.length > 0) {
      const nextTrack = queueRef.current[0]
      setActiveDownloadId(nextTrack.id)

      try {
        const res = await fetch(`/api/music/stream/${nextTrack.id}`)
        if (!res.ok) throw new Error(`Stream failed: ${res.status}`)
        const blob = await res.blob()

        await saveTrack(nextTrack.id, blob, nextTrack)

        // Sync to server playlist (non-critical)
        await fetch('/api/music/offline', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ songId: nextTrack.id }),
        }).catch(() => {})

        updateQueue((prev) => prev.filter((t) => t.id !== nextTrack.id))
        await refreshLocal()
      } catch (err) {
        console.error(`Failed to download ${nextTrack.id}:`, err)
        updateQueue((prev) => prev.filter((t) => t.id !== nextTrack.id))
      }
    }

    setActiveDownloadId(null)
    processingRef.current = false
  }, [refreshLocal, updateQueue])

  const sync = useCallback(async () => {
    try {
      setLoading(true)
      const res = await fetch('/api/music/offline')
      if (!res.ok) return
      const { songs } = await res.json() as { songs: Track[] }

      const localIds = await listOfflineIds()
      const localSet = new Set(localIds)
      const serverIds = new Set(songs.map((s: Track) => s.id))

      // Download songs on server but not local
      const toDownload = songs.filter((s: Track) => !localSet.has(s.id))
      if (toDownload.length > 0) {
        updateQueue((prev) => {
          const existingIds = new Set(prev.map((t) => t.id))
          return [...prev, ...toDownload.filter((t: Track) => !existingIds.has(t.id))]
        })
      }

      // Remove local songs deleted from server
      for (const localId of localIds) {
        if (!serverIds.has(localId)) {
          await removeFromDB(localId)
        }
      }

      await refreshLocal()
    } catch (err) {
      console.error('Offline sync error:', err)
    } finally {
      setLoading(false)
    }
  }, [refreshLocal, updateQueue])

  // Initial load + sync
  useEffect(() => {
    refreshLocal().then(() => sync())
  }, [refreshLocal, sync])

  // Process queue when items are added
  useEffect(() => {
    if (queue.length > 0 && !processingRef.current) {
      processQueue()
    }
  }, [queue, processQueue])

  const download = useCallback((track: Track) => {
    updateQueue((prev) => {
      if (prev.some((t) => t.id === track.id)) return prev
      return [...prev, track]
    })
  }, [updateQueue])

  const remove = useCallback(async (trackId: string) => {
    await removeFromDB(trackId)
    await fetch('/api/music/offline', {
      method: 'DELETE',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ songId: trackId }),
    }).catch(() => {})
    await refreshLocal()
  }, [refreshLocal])

  const clearAllOffline = useCallback(async () => {
    await clearDB()
    await refreshLocal()
  }, [refreshLocal])

  const getStatus = useCallback((trackId: string): DownloadStatus => {
    if (offlineIds.has(trackId)) return 'idle'
    if (activeDownloadId === trackId) return 'downloading'
    if (queue.some((t) => t.id === trackId)) return 'queued'
    return 'idle'
  }, [offlineIds, activeDownloadId, queue])

  return (
    <OfflineContext.Provider value={{
      offlineIds,
      queue,
      activeDownloadId,
      storageUsed,
      download,
      remove,
      clearAll: clearAllOffline,
      getStatus,
      sync,
      offlineTracks,
      loading,
    }}>
      {children}
    </OfflineContext.Provider>
  )
}
