import { queueSongStar } from '@/features/playback/store/pendingStarSync';
import { usePlaybackCoverArt } from '@/cover/usePlaybackCoverArt';
import { usePlaybackTrackCoverRef } from '@/cover/useLibraryCoverRef';
import type { Track } from '@/lib/media/trackTypes';
import { getPlaybackProgressSnapshot, subscribePlaybackProgress } from '@/features/playback/store/playbackProgress';
import React, { useState, useCallback, useRef, useEffect, useSyncExternalStore, CSSProperties } from 'react';
import { useNavigate } from 'react-router-dom';
import { usePlaybackLibraryNavigate } from '@/features/playback/hooks/usePlaybackLibraryNavigate';
import { useTranslation } from 'react-i18next';
import { useVirtualizer } from '@tanstack/react-virtual';
import {
  ChevronDown, Play, Pause, SkipBack, SkipForward,
  Shuffle, Repeat, Repeat1, Heart, Music, MicVocal, ListMusic, X,
  Moon, Sunrise,
} from 'lucide-react';
import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useCachedUrl } from '@/ui/CachedImage';
import { OpenArtistRefInline } from '@/ui/OpenArtistRefInline';
import { formatTrackTime } from '@/lib/format/formatDuration';
import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import {
  getQueueResolverVersion,
  subscribeQueueResolver,
  resolveVisibleRange,
} from '@/features/playback/store/queueTrackResolver';
import { LyricsPane } from '@/features/lyrics';
import { usePlaybackDelayPress } from '@/features/playback/hooks/usePlaybackDelayPress';
import PlaybackDelayModal from '@/features/playback/components/PlaybackDelayModal';
import PlaybackScheduleBadge from '@/features/playback/components/PlaybackScheduleBadge';
import { usePlaybackScheduleRemaining } from '@/features/playback/utils/playbackScheduleFormat';

// ── Color extraction ──────────────────────────────────────────────────────────
// Samples a 16×16 canvas to find the most vibrant (highest-saturation,
// medium-dark) pixel. Returns an "R, G, B" string for use in rgba().

function extractVibrantColor(imageUrl: string): Promise<string> {
  return new Promise(resolve => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = 16;
      canvas.height = 16;
      const ctx = canvas.getContext('2d');
      if (!ctx) { resolve('0,0,0'); return; }
      ctx.drawImage(img, 0, 0, 16, 16);
      const { data } = ctx.getImageData(0, 0, 16, 16);
      let bestR = 0, bestG = 0, bestB = 0, bestScore = -1;
      for (let i = 0; i < data.length; i += 4) {
        const r = data[i], g = data[i + 1], b = data[i + 2];
        const max = Math.max(r, g, b) / 255;
        const min = Math.min(r, g, b) / 255;
        const l = (max + min) / 2;
        const s = max === min ? 0 : (max - min) / (l > 0.5 ? 2 - max - min : max + min);
        // Prefer saturated pixels in the medium-dark range (l 0.2–0.6)
        const score = s * (1 - Math.abs(l - 0.4));
        if (score > bestScore) {
          bestScore = score;
          bestR = r; bestG = g; bestB = b;
        }
      }
      resolve(`${bestR},${bestG},${bestB}`);
    };
    img.onerror = () => resolve('0,0,0');
    img.src = imageUrl;
  });
}

function useAlbumAccentColor(imageUrl: string): string {
  const [color, setColor] = useState('0,0,0');
  useEffect(() => {
    // React Compiler set-state-in-effect rule: state set from a DOM/layout measurement.
    // eslint-disable-next-line react-hooks/set-state-in-effect
    if (!imageUrl) { setColor('0,0,0'); return; }
    let cancelled = false;
    extractVibrantColor(imageUrl).then(c => { if (!cancelled) setColor(c); });
    return () => { cancelled = true; };
  }, [imageUrl]);
  return color;
}

// ── Queue Drawer ──────────────────────────────────────────────────────────────

// Stable initial rect so the virtualizer never re-initializes on re-render (an
// inline literal would be a new ref each render → render loop). Replaced by the
// real height on first ResizeObserver measure.
const QUEUE_INITIAL_RECT = { width: 0, height: 600 };

function QueueDrawer({ onClose }: { onClose: () => void }) {
  const { t } = useTranslation();
  const queue = usePlayerStore(s => s.queueItems);
  const queueIndex = usePlayerStore(s => s.queueIndex);
  const playTrack = usePlayerStore(s => s.playTrack);
  const listRef = useRef<HTMLDivElement>(null);
  // Thin-state: the queue is the canonical `QueueItemRef[]`; each row's Track
  // comes from the resolver (cache → placeholder), matching the desktop
  // QueueList. Subscribe once so rows re-render as the resolver cache fills.
  useSyncExternalStore(subscribeQueueResolver, getQueueResolverVersion);

  // Virtualize so a multi-thousand-track queue keeps DOM at O(visible rows) on
  // mobile too (matches the desktop QueuePanel).
  // React Compiler incompatible-library rule: third-party hook/value the compiler cannot analyze; usage is correct.
  // eslint-disable-next-line react-hooks/incompatible-library
  const rowVirtualizer = useVirtualizer({
    count: queue.length,
    getScrollElement: () => listRef.current,
    estimateSize: () => 56,
    overscan: 10,
    getItemKey: i => `${queue[i].trackId}:${i}`,
    initialRect: QUEUE_INITIAL_RECT,
  });
  const virtualItems = rowVirtualizer.getVirtualItems();
  const totalSize = rowVirtualizer.getTotalSize();

  // Resolve the rows actually in view (queue thin-state). The resolver bridge only
  // warms a window around the playing index, so scrolling the drawer would leave
  // far-off rows stuck on the '…' placeholder. Drive resolution off the visible
  // range; resolveVisibleRange dedups against cache/in-flight.
  const firstVisible = virtualItems.length > 0 ? virtualItems[0]!.index : 0;
  const lastVisible = virtualItems.length > 0 ? virtualItems[virtualItems.length - 1]!.index : 0;
  useEffect(() => {
    if (queue.length === 0) return;
    resolveVisibleRange(queue, firstVisible, lastVisible);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [firstVisible, lastVisible, queue.length]);

  // Scroll the active track into view on open. Rows are uniform height, so the
  // virtualizer's estimate lands the centred index accurately.
  useEffect(() => {
    if (queueIndex >= 0 && queue.length > 0) {
      rowVirtualizer.scrollToIndex(queueIndex, { align: 'center' });
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div className="mq-drawer-backdrop" onClick={onClose}>
      <div className="mq-drawer" onClick={e => e.stopPropagation()}>
        <div className="mq-drawer-header">
          <h3>{t('queue.title')}</h3>
          <span className="mq-drawer-count">
            {queue.length} {queue.length === 1 ? t('queue.trackSingular') : t('queue.trackPlural')}
          </span>
          <button className="mq-drawer-close" onClick={onClose} aria-label="Close">
            <X size={20} />
          </button>
        </div>
        <div className="mq-drawer-list" ref={listRef}>
          {queue.length === 0 ? (
            <div className="mq-drawer-empty">{t('queue.emptyQueue')}</div>
          ) : (
            <div style={{ height: totalSize, width: '100%', position: 'relative' }}>
            {virtualItems.map(vi => {
              const idx = vi.index;
              const track = resolveQueueTrack(queue[idx]);
              const isActive = idx === queueIndex;
              return (
                <div
                  key={vi.key}
                  data-index={idx}
                  ref={rowVirtualizer.measureElement}
                  className={`mq-item${isActive ? ' active' : ''}`}
                  style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${vi.start}px)` }}
                  onClick={() => { playTrack(track, undefined, undefined, undefined, idx); onClose(); }}
                >
                  <div className="mq-item-info">
                    <div className="mq-item-title">
                      {isActive && <Play size={10} fill="currentColor" style={{ flexShrink: 0 }} />}
                      <span className="truncate">{track.title}</span>
                    </div>
                    <div className="mq-item-artist truncate">{track.artist}</div>
                  </div>
                  <span className="mq-item-dur">{formatTrackTime(track.duration)}</span>
                </div>
              );
            })}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ── Lyrics Drawer ─────────────────────────────────────────────────────────────

function LyricsDrawer({ onClose, currentTrack }: { onClose: () => void; currentTrack: Track | null }) {
  const { t } = useTranslation();

  return (
    <div className="mq-drawer-backdrop" onClick={onClose}>
      <div className="mq-drawer mq-drawer-lyrics" onClick={e => e.stopPropagation()}>
        <div className="mq-drawer-header">
          <h3>{t('player.lyrics')}</h3>
          <button className="mq-drawer-close" onClick={onClose} aria-label="Close">
            <X size={20} />
          </button>
        </div>
        <div className="mq-drawer-list">
          <LyricsPane currentTrack={currentTrack} />
        </div>
      </div>
    </div>
  );
}

// ── Mobile Player View ────────────────────────────────────────────────────────

export default function MobilePlayerView() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const navigatePlaybackLibrary = usePlaybackLibraryNavigate();
  // Lock body scroll while full-screen player is mounted
  useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, []);

  const currentTrack = usePlayerStore(s => s.currentTrack);
  const isPlaying    = usePlayerStore(s => s.isPlaying);
  const playbackProgress = useSyncExternalStore(
    onStoreChange => subscribePlaybackProgress(() => onStoreChange()),
    getPlaybackProgressSnapshot,
    getPlaybackProgressSnapshot,
  );
  const progress = playbackProgress.progress;
  const currentTime = playbackProgress.currentTime;
  const togglePlay   = usePlayerStore(s => s.togglePlay);
  const { delayModalOpen, setDelayModalOpen, playPauseBind } = usePlaybackDelayPress(togglePlay);
  const transportAnchorRef = useRef<HTMLDivElement>(null);
  const playSlotRef = useRef<HTMLSpanElement>(null);
  const scheduleRemaining = usePlaybackScheduleRemaining();
  const next         = usePlayerStore(s => s.next);
  const previous     = usePlayerStore(s => s.previous);
  const seek         = usePlayerStore(s => s.seek);
  const repeatMode   = usePlayerStore(s => s.repeatMode);
  const toggleRepeat = usePlayerStore(s => s.toggleRepeat);
  const shuffleQueue = usePlayerStore(s => s.shuffleQueue);
  const starredOverrides = usePlayerStore(s => s.starredOverrides);

  const duration = currentTrack?.duration ?? 0;

  const playbackCoverRef = usePlaybackTrackCoverRef(currentTrack ?? undefined);
  const { src: coverFetchUrl, cacheKey: coverKey } = usePlaybackCoverArt(playbackCoverRef, 800);
  const resolvedCover = useCachedUrl(coverFetchUrl, coverKey);
  const directCover = currentTrack?.directCoverArtUrl;
  const displayCover = directCover ?? resolvedCover;

  // Dynamic background color extracted from cover art
  const accentColor = useAlbumAccentColor(displayCover);

  // Star / favorite
  const isStarred = currentTrack
    ? (currentTrack.id in starredOverrides ? starredOverrides[currentTrack.id] : !!currentTrack.starred)
    : false;

  const toggleStar = useCallback(() => {
    if (!currentTrack) return;
    queueSongStar(currentTrack.id, !isStarred, currentTrack.serverId);
  }, [currentTrack, isStarred]);

  // Scrubber touch/mouse drag
  const scrubberRef = useRef<HTMLDivElement>(null);
  const isDragging = useRef(false);
  const pendingSeekRef = useRef<number | null>(null);
  const [previewProgress, setPreviewProgress] = useState<number | null>(null);

  const setPreviewSeek = useCallback((pct: number) => {
    pendingSeekRef.current = pct;
    setPreviewProgress(pct);
  }, []);

  const seekFromX = useCallback((clientX: number) => {
    const el = scrubberRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
    setPreviewSeek(pct);
  }, [setPreviewSeek]);

  const onScrubStart = useCallback((clientX: number) => {
    isDragging.current = true;
    seekFromX(clientX);
  }, [seekFromX]);

  useEffect(() => {
    const onMove = (e: TouchEvent | MouseEvent) => {
      if (!isDragging.current) return;
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
      seekFromX(clientX);
    };
    const onEnd = () => {
      if (!isDragging.current) return;
      isDragging.current = false;
      const pending = pendingSeekRef.current;
      pendingSeekRef.current = null;
      setPreviewProgress(null);
      if (pending !== null) seek(pending);
    };

    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onEnd);
    window.addEventListener('touchmove', onMove, { passive: true });
    window.addEventListener('touchend', onEnd);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onEnd);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('touchend', onEnd);
    };
  }, [seekFromX, seek]);

  useEffect(() => {
    pendingSeekRef.current = null;
    // React Compiler set-state-in-effect rule: state set from an external subscription/event callback.
    // eslint-disable-next-line react-hooks/set-state-in-effect
    setPreviewProgress(null);
  }, [currentTrack?.id]);

  // Drawers
  const [showQueue, setShowQueue] = useState(false);
  const [showLyrics, setShowLyrics] = useState(false);

  // ── Empty state ──
  if (!currentTrack) {
    return (
      <div className="mp-view">
        <div className="mp-header">
          <button className="mp-back" onClick={() => navigate(-1)} aria-label={t('player.back')}>
            <ChevronDown size={28} />
          </button>
          <span className="mp-header-title">{t('sidebar.nowPlaying')}</span>
          <div style={{ width: 44 }} />
        </div>
        <div className="mp-empty">
          <Music size={56} style={{ opacity: 0.25 }} />
          <p>{t('nowPlaying.nothingPlaying')}</p>
        </div>
      </div>
    );
  }

  const bgStyle: CSSProperties = {
    background: `radial-gradient(ellipse 160% 55% at 50% 20%, rgba(${accentColor}, 0.38) 0%, var(--bg-app) 65%)`,
  };
  const effectiveProgress = previewProgress ?? progress;
  const effectiveTime =
    previewProgress !== null && duration > 0
      ? previewProgress * duration
      : currentTime;

  return (
    <div className="mp-view" style={bgStyle}>
      {/* Header */}
      <div className="mp-header">
        <button className="mp-back" onClick={() => navigate(-1)} aria-label={t('player.back')}>
          <ChevronDown size={28} />
        </button>
        <span className="mp-header-title">{t('sidebar.nowPlaying')}</span>
        <div style={{ width: 44 }} />
      </div>

      {/* Cover Art */}
      <div className="mp-cover-wrap">
        {displayCover ? (
          <img src={displayCover} alt="" className="mp-cover" />
        ) : (
          <div className="mp-cover mp-cover-fallback">
            <Music size={64} />
          </div>
        )}
      </div>

      {/* Track Metadata */}
      <div className="mp-meta">
        <div className="mp-meta-text">
          <div className="mp-title truncate">{currentTrack.title}</div>
          <div className="mp-artist truncate">
            {currentTrack.artists && currentTrack.artists.length > 0 ? (
              <OpenArtistRefInline
                refs={currentTrack.artists}
                fallbackName={currentTrack.artist}
                onGoArtist={id => { void navigatePlaybackLibrary(`/artist/${id}`); }}
                as="none"
                linkTag="span"
                linkClassName="mp-artist-link"
              />
            ) : (
              <span
                role={currentTrack.artistId ? 'link' : undefined}
                tabIndex={currentTrack.artistId ? 0 : undefined}
                onClick={() => currentTrack.artistId && void navigatePlaybackLibrary(`/artist/${currentTrack.artistId}`)}
                onKeyDown={e => {
                  if (!currentTrack.artistId) return;
                  if (e.key === 'Enter' || e.key === ' ') {
                    e.preventDefault();
                    void navigatePlaybackLibrary(`/artist/${currentTrack.artistId}`);
                  }
                }}
                style={{ cursor: currentTrack.artistId ? 'pointer' : 'default' }}
              >
                {currentTrack.artist}
              </span>
            )}
          </div>
          {(() => {
            const parts = [
              currentTrack.year,
              currentTrack.genre,
              currentTrack.suffix?.toUpperCase(),
              currentTrack.bitRate ? `${currentTrack.bitRate} kbps` : null,
            ].filter(Boolean);
            return parts.length > 0
              ? <div className="mp-track-info truncate">{parts.join(' • ')}</div>
              : null;
          })()}
        </div>
        <button
          className={`mp-heart${isStarred ? ' active' : ''}`}
          onClick={toggleStar}
          aria-label={isStarred ? t('contextMenu.unfavorite') : t('contextMenu.favorite')}
        >
          <Heart size={22} fill={isStarred ? 'currentColor' : 'none'} />
        </button>
      </div>

      {/* Scrubber */}
      <div className="mp-scrubber-wrap">
        <div
          className="mp-scrubber"
          ref={scrubberRef}
          onMouseDown={e => onScrubStart(e.clientX)}
          onTouchStart={e => onScrubStart(e.touches[0].clientX)}
        >
          <div className="mp-scrubber-bg" />
          <div className="mp-scrubber-fill" style={{ width: `${effectiveProgress * 100}%` }} />
          <div className="mp-scrubber-thumb" style={{ left: `${effectiveProgress * 100}%` }} />
        </div>
        <div className="mp-scrubber-times">
          <span>{formatTrackTime(effectiveTime)}</span>
          <span>-{formatTrackTime(Math.max(0, duration - effectiveTime))}</span>
        </div>
      </div>

      {/* Transport Controls */}
      <div className="mp-controls" ref={transportAnchorRef}>
        <button
          className="mp-ctrl-btn mp-ctrl-sm"
          onClick={() => shuffleQueue()}
          aria-label={t('queue.shuffle')}
        >
          <Shuffle size={20} />
        </button>
        <button className="mp-ctrl-btn" onClick={() => previous()} aria-label={t('player.prev')}>
          <SkipBack size={28} />
        </button>
        <span className="playback-transport-play-wrap" ref={playSlotRef}>
          <PlaybackScheduleBadge layoutAnchorRef={playSlotRef} />
          <button
            className="mp-ctrl-btn mp-ctrl-play"
            type="button"
            {...playPauseBind}
            aria-label={isPlaying ? t('player.pause') : t('player.play')}
          >
            {scheduleRemaining != null ? (
              <span className={`player-btn-schedule-stack player-btn-schedule-stack--${scheduleRemaining.mode} player-btn-schedule-stack--mobile`}>
                {scheduleRemaining.mode === 'pause'
                  ? <Moon size={13} strokeWidth={2.5} />
                  : <Sunrise size={13} strokeWidth={2.5} />}
                <span className="player-btn-schedule-time player-btn-schedule-time--mobile">{scheduleRemaining.remaining}</span>
              </span>
            ) : isPlaying ? <Pause size={32} fill="currentColor" /> : <Play size={32} fill="currentColor" />}
          </button>
        </span>
        <button className="mp-ctrl-btn" onClick={() => next()} aria-label={t('player.next')}>
          <SkipForward size={28} />
        </button>
        <button
          className={`mp-ctrl-btn mp-ctrl-sm`}
          onClick={toggleRepeat}
          aria-label={t('player.repeat')}
          style={{ color: repeatMode !== 'off' ? 'var(--accent)' : undefined }}
        >
          {repeatMode === 'one' ? <Repeat1 size={20} /> : <Repeat size={20} />}
        </button>
      </div>

      {/* Utility Footer */}
      <div className="mp-footer">
        <button className="mp-footer-btn" onClick={() => setShowLyrics(true)}>
          <MicVocal size={20} />
          <span>{t('player.lyrics')}</span>
        </button>
        <button className="mp-footer-btn" onClick={() => setShowQueue(true)}>
          <ListMusic size={20} />
          <span>{t('queue.title')}</span>
        </button>
      </div>

      {/* Queue Drawer */}
      {showQueue && <QueueDrawer onClose={() => setShowQueue(false)} />}

      {/* Lyrics Drawer */}
      {showLyrics && <LyricsDrawer onClose={() => setShowLyrics(false)} currentTrack={currentTrack} />}

      <PlaybackDelayModal open={delayModalOpen} onClose={() => setDelayModalOpen(false)} anchorRef={transportAnchorRef} />
    </div>
  );
}
