import { create } from 'zustand';
import type { Track } from '../types';

export type RepeatMode = 'none' | 'all' | 'one';

/**
 * How many already-played tracks to keep behind the cursor. Bounds queue growth
 * from the Vibe prefetch loop while still leaving real history for prev().
 */
const MAX_HISTORY = 50;

interface PlaybackState {
  currentTrack: Track | null;
  queue: Track[];
  /** Cursor into `queue` for the current track, or -1 when the current track is not queued. */
  currentIndex: number;
  isPlaying: boolean;
  position: number;
  duration: number;
  volume: number;
  shuffle: boolean;
  repeat: RepeatMode;
  /** Ids already played this shuffle "lap" (repeat-all), to avoid bouncing between the same few tracks. */
  shufflePlayed: Set<string>;

  setQueue: (queue: Track[]) => void;
  playTrack: (track: Track) => void;
  play: () => void;
  pause: () => void;
  next: () => void;
  prev: () => void;
  setPosition: (position: number) => void;
  setDuration: (duration: number) => void;
  setVolume: (volume: number) => void;
  setCurrentTrack: (track: Track | null) => void;
  toggleShuffle: () => void;
  cycleRepeat: () => void;
}

/**
 * Move to `index` in `queue`, trimming stale history so the queue stays bounded.
 * Returns the state patch (queue may be re-sliced, so the index is adjusted).
 */
function advanceTo(queue: Track[], index: number) {
  let nextQueue = queue;
  let nextIndex = index;
  if (index > MAX_HISTORY) {
    const drop = index - MAX_HISTORY;
    nextQueue = queue.slice(drop);
    nextIndex = index - drop;
  }
  const track = nextQueue[nextIndex];
  return {
    queue: nextQueue,
    currentIndex: nextIndex,
    currentTrack: track,
    position: 0,
    duration: track?.duration ?? 0,
    isPlaying: true,
  };
}

export const usePlaybackStore = create<PlaybackState>((set, get) => ({
  currentTrack: null,
  queue: [],
  currentIndex: -1,
  isPlaying: false,
  position: 0,
  duration: 0,
  volume: 1,
  shuffle: false,
  repeat: 'none',
  shufflePlayed: new Set<string>(),

  setQueue: (queue) =>
    set((state) => ({
      queue,
      // Keep the cursor pointing at whatever is playing, if it is still queued.
      currentIndex: state.currentTrack ? queue.findIndex((t) => t.id === state.currentTrack!.id) : -1,
      shufflePlayed: new Set(),
    })),

  playTrack: (track) =>
    set((state) => ({
      currentTrack: track,
      currentIndex: state.queue.findIndex((t) => t.id === track.id),
      isPlaying: true,
      position: 0,
      duration: track.duration ?? 0,
      shufflePlayed: new Set(),
    })),

  play: () => set({ isPlaying: true }),
  pause: () => set({ isPlaying: false }),

  next: () => {
    const { queue, currentTrack, shuffle, repeat, shufflePlayed } = get();
    if (queue.length === 0) {
      set({ isPlaying: false, position: 0 });
      return;
    }

    // Prefer the tracked cursor; fall back to a lookup if it has drifted.
    const tracked = get().currentIndex;
    const idx =
      tracked >= 0 && queue[tracked]?.id === currentTrack?.id
        ? tracked
        : currentTrack
          ? queue.findIndex((t) => t.id === currentTrack.id)
          : -1;

    // Repeat one: replay current track
    if (repeat === 'one' && currentTrack) {
      set({ position: 0, isPlaying: true });
      return;
    }

    if (shuffle) {
      // Shuffle: pick a random track not yet played this lap (excludes current),
      // so repeat-all doesn't bounce between the same few tracks.
      const played = new Set(shufflePlayed);
      if (currentTrack) played.add(currentTrack.id);
      const indices = queue.map((_, i) => i);
      let remaining = indices.filter((i) => !played.has(queue[i].id));
      if (remaining.length === 0) {
        if (repeat !== 'all') {
          set({ isPlaying: false, position: 0 });
          return;
        }
        // Lap complete — start a fresh one.
        played.clear();
        if (currentTrack) played.add(currentTrack.id);
        remaining = indices.filter((i) => queue[i].id !== currentTrack?.id);
        if (remaining.length === 0) {
          set({ isPlaying: false, position: 0 });
          return;
        }
      }
      const pickIdx = remaining[Math.floor(Math.random() * remaining.length)];
      played.add(queue[pickIdx].id);
      const pick = queue[pickIdx];
      set({
        currentTrack: pick,
        currentIndex: pickIdx,
        shufflePlayed: played,
        position: 0,
        duration: pick.duration ?? 0,
        isPlaying: true,
      });
      return;
    }

    // Sequential — keep the queue intact and move the cursor, so prev() has
    // history and repeat-all restarts from the true first track.
    if (idx < 0) {
      // Nothing playing (or the current track left the queue) — start at the top.
      set(advanceTo(queue, 0));
    } else if (idx + 1 < queue.length) {
      set(advanceTo(queue, idx + 1));
    } else if (repeat === 'all' && queue.length > 0) {
      set(advanceTo(queue, 0));
    } else {
      // End of queue with no looping — stop playback so the store, DOM audio
      // element, and MediaSession all agree nothing is playing. Without this
      // the browser's now-playing widget can auto-resume the finished track
      // from its near-end position, causing a tight "play last second → end →
      // auto-resume → play last second → …" loop that also degrades performance.
      set({ isPlaying: false, position: 0 });
    }
  },

  prev: () => {
    const { queue, currentTrack, currentIndex } = get();
    if (queue.length === 0) return;
    const idx =
      currentIndex >= 0 && queue[currentIndex]?.id === currentTrack?.id
        ? currentIndex
        : currentTrack
          ? queue.findIndex((t) => t.id === currentTrack.id)
          : -1;
    if (idx > 0) {
      const prevTrack = queue[idx - 1];
      set({
        currentTrack: prevTrack,
        currentIndex: idx - 1,
        position: 0,
        duration: prevTrack.duration ?? 0,
        isPlaying: true,
      });
    }
  },

  setPosition: (position) => set({ position }),
  setDuration: (duration) => set({ duration }),
  setVolume: (volume) => set({ volume }),
  setCurrentTrack: (currentTrack) =>
    set((state) => ({
      currentTrack,
      currentIndex: currentTrack ? state.queue.findIndex((t) => t.id === currentTrack.id) : -1,
    })),

  toggleShuffle: () => set((state) => ({ shuffle: !state.shuffle })),
  cycleRepeat: () =>
    set((state) => {
      const modes: RepeatMode[] = ['none', 'all', 'one'];
      const next = modes[(modes.indexOf(state.repeat) + 1) % modes.length];
      return { repeat: next };
    }),
}));
