import { useCoverArt } from '../cover/useCoverArt';
import { albumCoverRef } from '../cover/ref';
import { usePlaybackTrackCoverRef } from '../cover/useLibraryCoverRef';
import { coverArtIdFromRadio } from '../cover/ids';
import type { SubsonicArtistInfo, SubsonicSong } from '../api/subsonicTypes';
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { usePlaybackLibraryNavigate } from '../hooks/usePlaybackLibraryNavigate';
import { usePlaybackServerId } from '../hooks/usePlaybackServerId';
import { useTranslation } from 'react-i18next';
import { Music, EyeOff, LayoutGrid, RotateCcw, Eye } from 'lucide-react';
import { usePlayerStore } from '../store/playerStore';
import { useAuthStore } from '../store/authStore';
import { useLyricsStore } from '../store/lyricsStore';
import { songToTrack } from '../utils/playback/songToTrack';
import { useRadioMetadata } from '../hooks/useRadioMetadata';
import { useDragDrop } from '../contexts/DragDropContext';
import OverlayScrollArea from '@/ui/OverlayScrollArea';
import {
  useNpLayoutStore, NP_CARD_IDS,
  type NpCardId, type NpColumn,
} from '../store/nowPlayingLayoutStore';

// ─── Helpers ──────────────────────────────────────────────────────────────────

import {
  buildContributorRows,
} from '../utils/componentHelpers/nowPlayingHelpers';
import NpCardWrap from '../components/nowPlaying/NpCardWrap';
import NpColumnEl from '../components/nowPlaying/NpColumnEl';
import RadioView from '../components/nowPlaying/RadioView';
import Hero from '../components/nowPlaying/Hero';
import ArtistCard from '../components/nowPlaying/ArtistCard';
import AlbumCard from '../components/nowPlaying/AlbumCard';
import TopSongsCard from '../components/nowPlaying/TopSongsCard';
import CreditsCard from '../components/nowPlaying/CreditsCard';
import TourCard from '../components/nowPlaying/TourCard';
import DiscographyCard from '../components/nowPlaying/DiscographyCard';
import { useNowPlayingFetchers } from '../hooks/useNowPlayingFetchers';
import { useNowPlayingStarLove } from '../hooks/useNowPlayingStarLove';
import { useArtistInfoBatch } from '../hooks/useArtistInfoBatch';
import { primaryTrackArtistRef, resolveTrackArtistRefs } from '../utils/playback/trackArtistRefs';
import type { ArtistCardTab } from '../components/nowPlaying/ArtistCard';

// ─── Main Page ────────────────────────────────────────────────────────────────

export default function NowPlaying() {
  const { t } = useTranslation();
  const stableNavigate = usePlaybackLibraryNavigate();
  const playbackServerId = usePlaybackServerId();
  const audiomuseNavidromeByServer = useAuthStore(s => s.audiomuseNavidromeByServer);
  const audiomuseNavidromeEnabled = Boolean(
    playbackServerId && audiomuseNavidromeByServer[playbackServerId],
  );

  const currentTrack            = usePlayerStore(s => s.currentTrack);
  const currentRadio            = usePlayerStore(s => s.currentRadio);
  const userRatingOverrides     = usePlayerStore(s => s.userRatingOverrides);
  const showLyrics              = useLyricsStore(s => s.showLyrics);
  const activeTab               = useLyricsStore(s => s.activeTab);
  const isQueueVisible          = usePlayerStore(s => s.isQueueVisible);
  const toggleQueue             = usePlayerStore(s => s.toggleQueue);
  const enableBandsintown    = useAuthStore(s => s.enableBandsintown);
  const setEnableBandsintown = useAuthStore(s => s.setEnableBandsintown);
  const enrichmentPrimaryId  = useAuthStore(s => s.enrichmentPrimaryId);
  const playTrackFn          = usePlayerStore(s => s.playTrack);

  const radioMeta = useRadioMetadata(currentRadio ?? null);

  const songId    = currentTrack?.id;
  const albumId   = currentTrack?.albumId;
  const trackArtistRefs = useMemo(
    () => (currentTrack ? resolveTrackArtistRefs(currentTrack) : []),
    [currentTrack],
  );
  const primaryArtist = useMemo(
    () => (currentTrack ? primaryTrackArtistRef(currentTrack) : null),
    [currentTrack],
  );
  const artistId = primaryArtist?.id;
  const artistName = primaryArtist?.name ?? currentTrack?.artist ?? '';

  // Entity fetchers (8 cached useEffects + their state)
  const {
    songMeta, artistInfo, albumData, topSongs,
    tourEvents, tourLoading, discography,
    networkTrack, networkArtist,
  } = useNowPlayingFetchers({
    songId, artistId, albumId, artistName,
    enableBandsintown, audiomuseNavidromeEnabled,
    enrichmentKey: enrichmentPrimaryId ?? '', currentTrack,
    subsonicServerId: playbackServerId,
    // `fetchEnabled` = "we have a playback server id". The reachability decision
    // (online / server reachable, no trackId so local-cache playback still loads
    // metadata) lives in one place inside the hook — see its JSDoc.
    fetchEnabled: Boolean(playbackServerId),
  });

  // Star + enrichment love + their toggle callbacks
  const networkLoveEnabled = enrichmentPrimaryId !== null;
  const { starred, networkLoved, toggleStar, toggleNetworkLove } = useNowPlayingStarLove({
    currentTrack, songMeta, networkTrack, networkLoveEnabled,
  });

  const openLyrics = useCallback(() => {
    if (!isQueueVisible) toggleQueue();
    showLyrics();
  }, [isQueueVisible, toggleQueue, showLyrics]);

  const playbackCoverRef = usePlaybackTrackCoverRef(currentTrack ?? undefined);

  const radioCoverArtId = currentRadio?.coverArt ? coverArtIdFromRadio(currentRadio.id) : undefined;
  const radioCover = useCoverArt(
    radioCoverArtId ? albumCoverRef(radioCoverArtId, radioCoverArtId) : null,
    800,
    { surface: 'sparse' },
  );
  const resolvedRadioCover = radioCover.src;

  const contributorRows = useMemo(
    () => buildContributorRows(songMeta, currentTrack?.artist ?? ''),
    [songMeta, currentTrack?.artist],
  );

  // Merge Subsonic artistInfo with the enrichment fallback: if Subsonic has no
  // bio, use the enrichment primary's artist bio so the card isn't empty.
  const effectiveArtistInfo = useMemo<SubsonicArtistInfo | null>(() => {
    if (!artistInfo && !networkArtist?.bio) return null;
    if (artistInfo?.biography) return artistInfo;
    if (!networkArtist?.bio) return artistInfo;
    return {
      ...(artistInfo ?? {}),
      biography: networkArtist.bio,
    };
  }, [artistInfo, networkArtist]);

  const artistInfoById = useArtistInfoBatch(
    playbackServerId,
    trackArtistRefs,
    audiomuseNavidromeEnabled ? 24 : undefined,
  );

  const artistTabs = useMemo((): ArtistCardTab[] | undefined => {
    const tabs = trackArtistRefs.filter(r => r.id?.trim());
    if (tabs.length <= 1) return undefined;
    return tabs.map((ref, index) => {
      const id = ref.id!.trim();
      const info = index === 0
        ? (effectiveArtistInfo ?? artistInfoById[id] ?? null)
        : (artistInfoById[id] ?? null);
      return {
        id,
        name: ref.name ?? id,
        artistInfo: info,
      };
    });
  }, [trackArtistRefs, artistInfoById, effectiveArtistInfo]);

  const handleEnableBandsintown = useCallback(() => setEnableBandsintown(true), [setEnableBandsintown]);

  const handlePlayTopSong = useCallback((song: SubsonicSong) => {
    if (topSongs.length === 0) return;
    const queue = topSongs.map(songToTrack);
    const hit = queue.find(q => q.id === song.id);
    if (hit) playTrackFn(hit, queue);
  }, [topSongs, playTrackFn]);

  // ── Widget layout (drag-to-reorder, hide/show, reset) ────────────────────
  const layoutCards   = useNpLayoutStore(s => s.cards);
  const moveCard      = useNpLayoutStore(s => s.moveCard);
  const setCardVisible = useNpLayoutStore(s => s.setVisible);
  const resetLayout   = useNpLayoutStore(s => s.reset);
  const { isDragging: dndActive, payload: dndPayload } = useDragDrop();

  const [dragOver, setDragOver] = useState<{ col: NpColumn; idx: number } | null>(null);
  const [layoutMenuOpen, setLayoutMenuOpen] = useState(false);

  // Parse the current drag payload to know whether it's an np-card drag
  const draggingCardId: NpCardId | null = useMemo(() => {
    if (!dndActive || !dndPayload) return null;
    try {
      const parsed = JSON.parse(dndPayload.data);
      if (parsed?.kind === 'np-card' && NP_CARD_IDS.includes(parsed.id)) return parsed.id as NpCardId;
    } catch { /* not a card payload */ }
    return null;
  }, [dndActive, dndPayload]);

  // Clear the drop indicator when the drag ends (no psy-drop on our target)
  // React Compiler set-state-in-effect rule: local state synced with store/prop inputs when the effect’s dependencies change.
  // eslint-disable-next-line react-hooks/set-state-in-effect
  useEffect(() => { if (!draggingCardId) setDragOver(null); }, [draggingCardId]);

  const toggleCardVisible = useCallback((id: NpCardId, next: boolean) => {
    setCardVisible(id, next);
  }, [setCardVisible]);

  const onColumnHover = useCallback((col: NpColumn, idx: number) => {
    setDragOver(prev => (prev && prev.col === col && prev.idx === idx) ? prev : { col, idx });
  }, []);

  // Ref mirror of dragOver so the document-level psy-drop handler always sees
  // the latest hovered column/index regardless of closure timing.
  const dragOverRef = useRef(dragOver);
  // React Compiler refs rule: ref kept in sync with the latest value for use in effects/handlers/cleanup; not render data.
  // eslint-disable-next-line react-hooks/refs
  dragOverRef.current = dragOver;

  // Global psy-drop listener: catches drops anywhere on the page (even below a
  // column when the cursor left the column bounds), then uses dragOverRef to
  // decide which column/index the user actually meant.
  useEffect(() => {
    if (!draggingCardId) return;
    const onPsyDrop = (evt: Event) => {
      const ce = evt as CustomEvent<{ data: string }>;
      try {
        const parsed = JSON.parse(ce.detail?.data ?? '');
        if (parsed?.kind !== 'np-card' || !NP_CARD_IDS.includes(parsed.id)) return;
        const over = dragOverRef.current;
        if (over) {
          moveCard(parsed.id as NpCardId, over.col, over.idx);
        }
      } catch { /* ignore non-card drops */ }
      setDragOver(null);
    };
    document.addEventListener('psy-drop', onPsyDrop as EventListener);
    return () => document.removeEventListener('psy-drop', onPsyDrop as EventListener);
  }, [draggingCardId, moveCard]);

  // Close layout menu on outside click
  useEffect(() => {
    if (!layoutMenuOpen) return;
    const onDoc = (e: MouseEvent) => {
      const el = e.target as HTMLElement | null;
      if (!el?.closest('.np-dash-toolbar-menu-wrap')) setLayoutMenuOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [layoutMenuOpen]);

  // ── Render ────────────────────────────────────────────────────────────────
  return (
    <div className="np-page">
      <OverlayScrollArea
        className="np-main"
        viewportClassName="np-main__viewport"
        railInset="panel"
        measureDeps={[
          !!currentTrack,
          !!currentRadio,
          layoutCards,
          enableBandsintown,
          tourEvents.length,
          discography.length,
          topSongs.length,
        ]}
      >
        {currentRadio && !currentTrack ? (
          <RadioView radioMeta={radioMeta} currentRadio={currentRadio} resolvedCover={resolvedRadioCover} />
        ) : currentTrack ? (
          <div className="np-dash">
            <Hero
              track={{
                id: currentTrack.id,
                title: currentTrack.title,
                artist: currentTrack.artist,
                album: currentTrack.album,
                year: currentTrack.year,
                duration: currentTrack.duration,
                suffix: currentTrack.suffix,
                bitRate: currentTrack.bitRate,
                samplingRate: songMeta?.samplingRate,
                bitDepth: songMeta?.bitDepth,
                artistId: currentTrack.artistId,
                albumId: currentTrack.albumId,
                userRating: currentTrack.userRating,
              }}
              artistRefs={trackArtistRefs.length > 0 ? trackArtistRefs : undefined}
              genre={songMeta?.genre ?? undefined}
              playCount={(songMeta as (SubsonicSong & { playCount?: number }) | null)?.playCount}
              userRatingOverride={userRatingOverrides[currentTrack.id]}
              networkTrack={networkTrack}
              networkArtist={networkArtist}
              starred={starred}
              networkLoved={networkLoved}
              networkLoveEnabled={networkLoveEnabled}
              activeLyricsTab={activeTab === 'lyrics' && isQueueVisible}
              coverRef={playbackCoverRef}
              onNavigate={stableNavigate}
              onToggleStar={toggleStar}
              onToggleNetworkLove={toggleNetworkLove}
              onOpenLyrics={openLyrics}
            />

            {(() => {
              const renderCard = (id: NpCardId): React.ReactNode => {
                switch (id) {
                  case 'album': return (
                    <AlbumCard
                      album={albumData?.album ?? null}
                      songs={albumData?.songs ?? []}
                      currentTrackId={currentTrack.id}
                      albumName={currentTrack.album}
                      albumId={albumId}
                      albumYear={currentTrack.year}
                      onNavigate={stableNavigate}
                    />
                  );
                  case 'topSongs': return (
                    <TopSongsCard
                      artistName={artistName}
                      artistId={artistId}
                      songs={topSongs}
                      currentTrackId={currentTrack.id}
                      onNavigate={stableNavigate}
                      onPlay={handlePlayTopSong}
                    />
                  );
                  case 'credits': return <CreditsCard rows={contributorRows} />;
                  case 'artist': return (
                    <ArtistCard
                      artistName={artistName}
                      artistId={artistId}
                      artistInfo={effectiveArtistInfo}
                      artistTabs={artistTabs}
                      onNavigate={stableNavigate}
                    />
                  );
                  case 'discography': return (
                    <DiscographyCard
                      artistId={artistId}
                      albums={discography}
                      currentAlbumId={albumId}
                      onNavigate={stableNavigate}
                    />
                  );
                  case 'tour': return (
                    <TourCard
                      artistName={artistName}
                      enabled={enableBandsintown}
                      loading={tourLoading}
                      events={tourEvents}
                      onEnable={handleEnableBandsintown}
                    />
                  );
                }
              };
              const cardLabel = (id: NpCardId): string => {
                const k: Record<NpCardId, string> = {
                  album: 'nowPlaying.fromAlbum',
                  topSongs: 'nowPlaying.topSongs',
                  credits: 'nowPlayingInfo.songInfo',
                  artist: 'nowPlaying.aboutArtist',
                  discography: 'nowPlaying.discography',
                  tour: 'nowPlayingInfo.onTour',
                };
                return t(k[id]);
              };
              const visibleCards = layoutCards.filter(c => c.visible);
              const hiddenCards  = layoutCards.filter(c => !c.visible);
              const renderColumn = (col: NpColumn) => {
                const cards = layoutCards.filter(c =>
                  c.column === col && c.visible && c.id !== draggingCardId,
                );
                const isOver = dragOver?.col === col;
                return (
                  <NpColumnEl
                    col={col}
                    empty={cards.length === 0}
                    emptyLabel={t('nowPlaying.emptyColumn', 'Drop cards here')}
                    isDndActive={!!draggingCardId}
                    draggingCardId={draggingCardId}
                    onHover={onColumnHover}
                    isOverHere={!!isOver}
                  >
                    {cards.map((c, idx) => (
                      <React.Fragment key={c.id}>
                        {isOver && dragOver.idx === idx && <div className="np-dash-drop-indicator" />}
                        <NpCardWrap
                          id={c.id}
                          label={cardLabel(c.id)}
                          isDraggingThis={draggingCardId === c.id}
                        >
                          {renderCard(c.id)}
                        </NpCardWrap>
                      </React.Fragment>
                    ))}
                    {isOver && dragOver.idx === cards.length && <div className="np-dash-drop-indicator" />}
                  </NpColumnEl>
                );
              };
              return (
                <>
                  <div className="np-dash-toolbar">
                    <div className="np-dash-toolbar-menu-wrap">
                      <button
                        className="np-dash-toolbar-btn"
                        onClick={() => setLayoutMenuOpen(v => !v)}
                        data-tooltip={t('nowPlaying.layoutMenu', 'Layout')}
                      >
                        <LayoutGrid size={14} />
                        <span>{t('nowPlaying.layoutMenu', 'Layout')}</span>
                        {hiddenCards.length > 0 && (
                          <span className="np-dash-toolbar-badge">{hiddenCards.length}</span>
                        )}
                      </button>
                      {layoutMenuOpen && (
                        <div className="np-dash-toolbar-menu" role="menu">
                          <div className="np-dash-toolbar-section">
                            {t('nowPlaying.visibleCards', 'Visible cards')}
                          </div>
                          {visibleCards.map(c => (
                            <button
                              key={c.id}
                              className="np-dash-toolbar-item"
                              onClick={() => toggleCardVisible(c.id, false)}
                            >
                              <Eye size={13} /> <span className="np-dash-toolbar-item-label">{cardLabel(c.id)}</span>
                            </button>
                          ))}
                          {hiddenCards.length > 0 && (
                            <>
                              <div className="np-dash-toolbar-section">
                                {t('nowPlaying.hiddenCards', 'Hidden cards')}
                              </div>
                              {hiddenCards.map(c => (
                                <button
                                  key={c.id}
                                  className="np-dash-toolbar-item is-hidden"
                                  onClick={() => toggleCardVisible(c.id, true)}
                                >
                                  <EyeOff size={13} /> <span className="np-dash-toolbar-item-label">{cardLabel(c.id)}</span>
                                </button>
                              ))}
                            </>
                          )}
                          <div className="np-dash-toolbar-divider" />
                          <button
                            className="np-dash-toolbar-item"
                            onClick={() => { resetLayout(); setLayoutMenuOpen(false); }}
                          >
                            <RotateCcw size={13} /> <span className="np-dash-toolbar-item-label">{t('nowPlaying.resetLayout', 'Reset layout')}</span>
                          </button>
                        </div>
                      )}
                    </div>
                  </div>
                  <div className="np-dash-grid">
                    {renderColumn('left')}
                    {renderColumn('right')}
                  </div>
                </>
              );
            })()}
          </div>
        ) : (
          <div className="np-empty-state">
            <Music size={48} style={{ opacity: 0.3 }} />
            <p>{t('nowPlaying.nothingPlaying')}</p>
          </div>
        )}
      </OverlayScrollArea>
    </div>
  );
}
