import { useEffect, useRef, useState, type MouseEvent as ReactMouseEvent } from 'react'
import './furumi-player.css'
import { API_ROOT, searchTracks, preloadStream } from './furumiApi'
import { store, useAppDispatch, useAppSelector } from './store'
import { fetchArtists } from './store/slices/artistsSlice'
import { fetchArtistAlbums } from './store/slices/albumsSlice'
import { fetchArtistTracks } from './store/slices/artistTracksSlice'
import { fetchAlbumTracks } from './store/slices/albumTracksSlice'
import { fetchTrackDetail } from './store/slices/trackDetailSlice'
import {
  addTrack,
  addTracksBatch,
  replaceQueue,
  clearQueue,
  playAtIndex,
  removeFromQueueAt,
  moveQueueItemInOrder,
  toggleShuffle,
  toggleRepeat,
  rebuildShuffleOrder,
  selectQueueOrder,
  selectPlayingOrigIdx,
  selectQueueScrollSignal,
  selectNowPlayingTrack,
  selectQueueItems,
} from './store/slices/queueSlice'
import { attachAudioPlayback } from './audioPlaybackService'
import { fmt } from './utils'
import { Header } from './components/header'
import { MainPanel, type Crumb } from './components/MainPanel'
import { PlayerBar } from './components/PlayerBar'
import type { Track } from './types'

export function FurumiPlayer() {
  const dispatch = useAppDispatch()
  const artistsLoading = useAppSelector((s) => s.artists.loading)
  const artistsError = useAppSelector((s) => s.artists.error)
  const albumsLoading = useAppSelector((s) => s.albums.loading)
  const albumsError = useAppSelector((s) => s.albums.error)
  const albumTracksLoading = useAppSelector((s) => s.albumTracks.loading)
  const albumTracksError = useAppSelector((s) => s.albumTracks.error)

  const queueItemsView = useAppSelector(selectQueueItems)
  const queueOrderView = useAppSelector(selectQueueOrder)
  const queuePlayingOrigIdxView = useAppSelector(selectPlayingOrigIdx)
  const queueScrollSignal = useAppSelector(selectQueueScrollSignal)
  const nowPlayingTrack = useAppSelector(selectNowPlayingTrack)

  const [breadcrumbs, setBreadcrumbs] = useState<Crumb[]>(
    [],
  )
  const [libraryItems, setLibraryItems] = useState<
    Array<{
      key: string
      className: string
      icon: string
      name: string
      detail?: string
      nameClassName?: string
      onClick: () => void
      button?: { title: string; onClick: (ev: ReactMouseEvent<HTMLButtonElement>) => void }
    }>
  >([])
  const [searchResults, setSearchResults] = useState<
    Array<{ result_type: string; slug: string; name: string; detail?: string }>
  >([])
  const [searchOpen, setSearchOpen] = useState(false)
  const searchSelectRef = useRef<(type: string, slug: string) => void>(() => { })

  const queueActionsRef = useRef<{
    playIndex: (i: number) => void
    removeFromQueue: (idx: number) => void
    moveQueueItem: (fromPos: number, toPos: number) => void
  } | null>(null)

  const audioRef = useRef<HTMLAudioElement>(null)

  useEffect(() => {
    if (!nowPlayingTrack) {
      document.title = 'Furumi Player'
      return
    }
    document.title = `${nowPlayingTrack.title} — Furumi`
    const coverUrl = `${API_ROOT}/tracks/${nowPlayingTrack.slug}/cover`
    if ('mediaSession' in navigator) {
      try {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        navigator.mediaSession.metadata = new window.MediaMetadata({
          title: nowPlayingTrack.title,
          artist: nowPlayingTrack.artist || '',
          album: '',
          artwork: [{ src: coverUrl, sizes: '512x512' }],
        })
      } catch {
        // ignore
      }
    }
  }, [nowPlayingTrack])

  const shuffle = useAppSelector((s) => s.queue.shuffle)
  const repeatAll = useAppSelector((s) => s.queue.repeatAll)

  useEffect(() => {
    const btnShuffle = document.getElementById('btnShuffle')
    const btnRepeat = document.getElementById('btnRepeat')
    btnShuffle?.classList.toggle('active', shuffle)
    btnRepeat?.classList.toggle('active', repeatAll)
  }, [shuffle, repeatAll])

  useEffect(() => {
    const audioEl = audioRef.current
    if (!audioEl) return
    const audio = audioEl

    let searchTimer: number | null = null
    let toastTimer: number | null = null

    function showToast(msg: string) {
      const t = document.getElementById('toast')
      if (!t) return
      t.textContent = msg
      t.classList.add('show')
      if (toastTimer) window.clearTimeout(toastTimer)
      toastTimer = window.setTimeout(() => t.classList.remove('show'), 2500)
    }

    async function showArtists() {
      setBreadcrumb([{ label: 'Artists', action: showArtists }])
      try {
        const artists = await dispatch(fetchArtists()).unwrap()
        setLibraryItems(
          artists.map((a) => ({
            key: `artist:${a.slug}`,
            className: 'file-item dir',
            icon: '👤',
            name: a.name,
            detail: `${a.album_count} albums`,
            onClick: () => void showArtistAlbums(a.slug, a.name),
          })),
        )
      } catch {
        // Error is stored in artists.error
      }
    }

    async function showArtistAlbums(artistSlug: string, artistName: string) {
      setBreadcrumb([
        { label: 'Artists', action: showArtists },
        { label: artistName, action: () => showArtistAlbums(artistSlug, artistName) },
      ])
      try {
        const { albums } = await dispatch(fetchArtistAlbums(artistSlug)).unwrap()
        const allTracksItem = {
          key: `artist-all:${artistSlug}`,
          className: 'file-item',
          icon: '▶',
          name: 'Play all tracks',
          nameClassName: 'name',
          onClick: () => void playAllArtistTracks(artistSlug),
        }
        const albumItems = albums.map((a) => {
          const year = a.year ? ` (${a.year})` : ''
          return {
            key: `album:${a.slug}`,
            className: 'file-item dir',
            icon: '💿',
            name: `${a.name}${year}`,
            detail: `${a.track_count} tracks`,
            onClick: () => void showAlbumTracks(a.slug, a.name, artistSlug, artistName),
            button: {
              title: 'Add album to queue',
              onClick: (ev: ReactMouseEvent<HTMLButtonElement>) => {
                ev.stopPropagation()
                void addAlbumToQueue(a.slug)
              },
            },
          }
        })
        setLibraryItems([allTracksItem, ...albumItems])
      } catch {
        // Error is stored in albums.error
      }
    }

    async function showAlbumTracks(
      albumSlug: string,
      albumName: string,
      artistSlug: string,
      artistName: string,
    ) {
      setBreadcrumb([
        { label: 'Artists', action: showArtists },
        { label: artistName, action: () => showArtistAlbums(artistSlug, artistName) },
        { label: albumName },
      ])
      const result = await dispatch(fetchAlbumTracks(albumSlug))
      if (result.meta.requestStatus === 'rejected') return
      const { tracks } = result.payload as { albumSlug: string; tracks: Track[] }
      const playAlbumItem = {
        key: `album-play:${albumSlug}`,
        className: 'file-item',
        icon: '▶',
        name: 'Play album',
        onClick: () => {
          void addAlbumToQueue(albumSlug, true)
        },
      }
      const trackItems = tracks.map((t) => {
        const num = t.track_number ? `${t.track_number}. ` : ''
        const dur = t.duration_secs ? fmt(t.duration_secs) : ''
        return {
          key: `track:${t.slug}`,
          className: 'file-item',
          icon: '🎵',
          name: `${num}${t.title}`,
          detail: dur,
          onClick: () => {
            addTrackToQueue(
              {
                slug: t.slug,
                title: t.title,
                artist: t.artist_name,
                album_slug: albumSlug,
                duration: t.duration_secs,
              },
              true,
            )
          },
        }
      })
      setLibraryItems([playAlbumItem, ...trackItems])
    }

    function setBreadcrumb(parts: Crumb[]) {
      setBreadcrumbs(parts)
    }

    function addTrackToQueue(
      track: {
        slug: string
        title: string
        artist: string
        album_slug: string | null
        duration: number | null
      },
      playNow?: boolean,
    ) {
      const prevIdx = store.getState().queue.currentIndex
      dispatch(addTrack({ track, playNow }))
      const q = store.getState().queue
      if (q.currentIndex !== -1 && (playNow || q.currentIndex !== prevIdx)) {
        playIndex(q.currentIndex)
      }
    }

    async function addAlbumToQueue(albumSlug: string, playFirst?: boolean) {
      const result = await dispatch(fetchAlbumTracks(albumSlug))
      if (result.meta.requestStatus === 'rejected') return
      const { tracks } = result.payload as { albumSlug: string; tracks: Track[] }
      if (!tracks || !tracks.length) return
      const list = tracks.map((t) => ({
        slug: t.slug,
        title: t.title,
        artist: t.artist_name,
        album_slug: t.album_slug,
        duration: t.duration_secs,
      }))
      const prevIdx = store.getState().queue.currentIndex
      dispatch(addTracksBatch({ tracks: list, playFirst }))
      const q = store.getState().queue
      if (q.currentIndex !== -1 && q.currentIndex !== prevIdx) {
        playIndex(q.currentIndex)
      }
      showToast(`Added ${list.length} tracks`)
    }

    async function playAllArtistTracks(artistSlug: string) {
      const result = await dispatch(fetchArtistTracks(artistSlug))
      if (result.meta.requestStatus === 'rejected') return
      const { tracks } = result.payload as { artistSlug: string; tracks: Track[] }
      if (!tracks || !tracks.length) return
      const list = tracks.map((t) => ({
        slug: t.slug,
        title: t.title,
        artist: t.artist_name,
        album_slug: t.album_slug,
        duration: t.duration_secs,
      }))
      dispatch(replaceQueue({ items: list, playFromIndex: 0 }))
      playIndex(0)
      showToast(`Added ${list.length} tracks`)
    }

    const playback = attachAudioPlayback(audio, {
      onEnded: nextTrack,
      onErrorSkip: nextTrack,
      onToast: showToast,
    })

    function playIndex(i: number) {
      const q = store.getState().queue
      if (i < 0 || i >= q.items.length) return
      dispatch(playAtIndex(i))
      const track = store.getState().queue.items[i]
      void playback.loadStreamForTrack(track.slug)
      if (window.history && window.history.replaceState) {
        const url = new URL(window.location.href)
        url.searchParams.set('t', track.slug)
        window.history.replaceState(null, '', url.toString())
      }
    }

    function removeFromQueue(idx: number) {
      const wasPlaying = store.getState().queue.currentIndex === idx
      dispatch(removeFromQueueAt(idx))
      if (wasPlaying) playback.pauseAndClearSource()
    }

    function moveQueueItem(fromPos: number, toPos: number) {
      dispatch(moveQueueItemInOrder({ fromPos, toPos }))
    }

    function clearQueuePlayback() {
      dispatch(clearQueue())
      playback.pauseAndClearSource()
    }

    function nextTrack() {
      const q = store.getState().queue
      if (!q.items.length) return
      const order = selectQueueOrder(store.getState())
      const pos = order.indexOf(q.currentIndex)
      if (pos < order.length - 1) playIndex(order[pos + 1])
      else if (q.repeatAll) {
        if (q.shuffle) dispatch(rebuildShuffleOrder())
        const first = selectQueueOrder(store.getState())[0]
        if (first !== undefined) playIndex(first)
      }
    }

    function prevTrack() {
      const q = store.getState().queue
      if (!q.items.length) return
      if (playback.rewindCurrentTrackIfPastThreshold()) return
      const order = selectQueueOrder(store.getState())
      const pos = order.indexOf(q.currentIndex)
      if (pos > 0) playIndex(order[pos - 1])
      else if (q.repeatAll) playIndex(order[order.length - 1])
    }

    function togglePlay() {
      const q = store.getState().queue
      playback.togglePlay(() => {
        playIndex(q.currentIndex === -1 ? 0 : q.currentIndex)
      })
    }

    queueActionsRef.current = {
      playIndex,
      removeFromQueue,
      moveQueueItem,
    }

    function onToggleShuffle() {
      dispatch(toggleShuffle())
    }

    function onToggleRepeat() {
      dispatch(toggleRepeat())
    }

    function onSearch(q: string) {
      if (searchTimer) {
        window.clearTimeout(searchTimer)
      }
      if (q.length < 2) {
        closeSearch()
        return
      }
      searchTimer = window.setTimeout(async () => {
        const results = await searchTracks(q)
        if (!results || !(results as any[]).length) {
          closeSearch()
          return
        }
        setSearchResults(results as any[])
        setSearchOpen(true)
      }, 250)
    }

    function closeSearch() {
      setSearchOpen(false)
      setSearchResults([])
    }

    function onSearchSelect(type: string, slug: string) {
      closeSearch()
      if (type === 'artist') void showArtistAlbums(slug, '')
      else if (type === 'album') void addAlbumToQueue(slug, true)
      else if (type === 'track') {
        addTrackToQueue(
          { slug, title: '', artist: '', album_slug: null, duration: null },
          true,
        )
        void preloadStream(slug)
      }
    }
    searchSelectRef.current = onSearchSelect

    function toggleSidebar() {
      const sidebar = document.getElementById('sidebar')
      const overlay = document.getElementById('sidebarOverlay')
      sidebar?.classList.toggle('open')
      overlay?.classList.toggle('show')
    }

    const onMediaSeekTo = (d: { seekTime?: number }) => {
      if (typeof d.seekTime === 'number') {
        playback.seekToTime(d.seekTime)
      }
    }

    if ('mediaSession' in navigator) {
      try {
        navigator.mediaSession.setActionHandler('play', togglePlay)
        navigator.mediaSession.setActionHandler('pause', togglePlay)
        navigator.mediaSession.setActionHandler('previoustrack', prevTrack)
        navigator.mediaSession.setActionHandler('nexttrack', nextTrack)
        navigator.mediaSession.setActionHandler('seekto', onMediaSeekTo as (d: any) => void)
      } catch {
        // ignore
      }
    }

    const onMenuClick = () => toggleSidebar()
    const btnMenu = document.querySelector('.btn-menu')
    btnMenu?.addEventListener('click', onMenuClick)

    const onSidebarOverlayClick = () => toggleSidebar()
    const sidebarOverlay = document.getElementById('sidebarOverlay')
    sidebarOverlay?.addEventListener('click', onSidebarOverlayClick)

    const searchInput = document.getElementById('searchInput') as HTMLInputElement | null
    const onSearchInput = (e: Event) => {
      onSearch((e.target as HTMLInputElement).value)
    }
    const onSearchKeydown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') closeSearch()
    }
    if (searchInput) {
      searchInput.addEventListener('input', onSearchInput)
      searchInput.addEventListener('keydown', onSearchKeydown)
    }

    const onShuffleClick = () => onToggleShuffle()
    const onRepeatClick = () => onToggleRepeat()
    const onClearClick = () => clearQueuePlayback()
    const onPrevClick = () => prevTrack()
    const onPlayClick = () => togglePlay()
    const onNextClick = () => nextTrack()

    const btnShuffle = document.getElementById('btnShuffle')
    btnShuffle?.addEventListener('click', onShuffleClick)
    const btnRepeat = document.getElementById('btnRepeat')
    btnRepeat?.addEventListener('click', onRepeatClick)
    const btnClear = document.getElementById('btnClearQueue')
    btnClear?.addEventListener('click', onClearClick)

    const btnPrev = document.getElementById('btnPrev')
    btnPrev?.addEventListener('click', onPrevClick)
    const btnPlay = document.getElementById('btnPlayPause')
    btnPlay?.addEventListener('click', onPlayClick)
    const btnNext = document.getElementById('btnNext')
    btnNext?.addEventListener('click', onNextClick)

      ; (async () => {
        const url = new URL(window.location.href)
        const urlSlug = url.searchParams.get('t')
        if (urlSlug) {
          try {
            const { detail } = await dispatch(fetchTrackDetail(urlSlug)).unwrap()
            addTrackToQueue(
              {
                slug: detail.slug,
                title: detail.title,
                artist: detail.artist_name,
                album_slug: detail.album_slug,
                duration: detail.duration_secs,
              },
              true,
            )
          } catch {
            // fetchTrackDetail rejected — track not found or error
          }
        }
        void showArtists()
      })()

    return () => {
      queueActionsRef.current = null
      playback.dispose()
      btnMenu?.removeEventListener('click', onMenuClick)
      sidebarOverlay?.removeEventListener('click', onSidebarOverlayClick)
      searchInput?.removeEventListener('input', onSearchInput)
      searchInput?.removeEventListener('keydown', onSearchKeydown)
      btnShuffle?.removeEventListener('click', onShuffleClick)
      btnRepeat?.removeEventListener('click', onRepeatClick)
      btnClear?.removeEventListener('click', onClearClick)
      btnPrev?.removeEventListener('click', onPrevClick)
      btnPlay?.removeEventListener('click', onPlayClick)
      btnNext?.removeEventListener('click', onNextClick)
      if ('mediaSession' in navigator) {
        try {
          navigator.mediaSession.setActionHandler('play', null)
          navigator.mediaSession.setActionHandler('pause', null)
          navigator.mediaSession.setActionHandler('previoustrack', null)
          navigator.mediaSession.setActionHandler('nexttrack', null)
          navigator.mediaSession.setActionHandler('seekto', null)
        } catch {
          // ignore
        }
      }
    }
  }, [dispatch])

  const libraryLoading =
    breadcrumbs.length === 1
      ? artistsLoading
      : breadcrumbs.length === 2
        ? albumsLoading
        : albumTracksLoading

  const libraryError =
    breadcrumbs.length === 1
      ? artistsError
      : breadcrumbs.length === 2
        ? albumsError
        : albumTracksError

  return (
    <div className="furumi-root">
      <Header
        searchOpen={searchOpen}
        searchResults={searchResults}
        onSearchSelect={(type, slug) => searchSelectRef.current(type, slug)}
      />

      <MainPanel
        breadcrumbs={breadcrumbs}
        libraryLoading={libraryLoading}
        libraryError={libraryError}
        libraryItems={libraryItems}
        queueItemsView={queueItemsView}
        queueOrderView={queueOrderView}
        queuePlayingOrigIdxView={queuePlayingOrigIdxView}
        queueScrollSignal={queueScrollSignal}
        onQueuePlay={(origIdx) => queueActionsRef.current?.playIndex(origIdx)}
        onQueueRemove={(origIdx) =>
          queueActionsRef.current?.removeFromQueue(origIdx)
        }
        onQueueMove={(fromPos, toPos) =>
          queueActionsRef.current?.moveQueueItem(fromPos, toPos)
        }
      />

      <PlayerBar
        track={nowPlayingTrack}
        queue={queueItemsView}
        order={queueOrderView}
        playingOrigIdx={queuePlayingOrigIdxView}
        scrollSignal={queueScrollSignal}
        onQueuePlay={(origIdx) => queueActionsRef.current?.playIndex(origIdx)}
        onQueueRemove={(origIdx) =>
          queueActionsRef.current?.removeFromQueue(origIdx)
        }
        onQueueMove={(fromPos, toPos) =>
          queueActionsRef.current?.moveQueueItem(fromPos, toPos)
        }
      />

      <div className="toast" id="toast" />
      <audio ref={audioRef} />
    </div>
  )
}
