import { reaction, runInAction } from 'mobx';

import { ContextType } from '@/logging/contextTypes';

import { Clip } from './clipStore';
import { RootStore, Substore } from './rootStore';
import { makeAutoObservableSubstore } from './utils';

export type PlayContext = {
  clips: Clip[];
  pendingClips?: Clip[];
  currentIndex?: number;
  clipIndexOrder?: number[];

  // playlist ID, radio tag, song ID for recommendations, etc.
  currentPlayingSongIsRemoved?: boolean;
  contextId: string;
  contextType: ContextType;
  surfaceType?: string;
  surfaceId?: string;
};

export type QueueItem = {
  clip: Clip;

  // Recommendation metadata (only used for autoplay queue)
  recId?: string;
  recMetadata?: any;

  // TODO: Add contextType and contextId when we implement proper source context tracking
  // This will require updating ClipContext to pass down the source context from parent components
  // contextType?: ContextType;
  // contextId?: string;
};

const NUM_MOST_RECENT_CLIPS_IN_QUEUE = 50;
const MIN_QUEUE_ITEMS_REMAINING_BEFORE_REFRESH = 2;
const QUEUE_STORAGE_KEY = 'queue-state';

function indexRange(size: number, randomize = false) {
  const indexes = Array.from({ length: size }, (_, i) => i);
  if (randomize) {
    for (let i = size - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [indexes[i], indexes[j]] = [indexes[j], indexes[i]];
    }
  }
  return indexes;
}

export const autoAssignPlayContextIndex = (
  playContext: PlayContext,
  clipId: string
) => {
  const index = playContext.clips.findIndex((c: Clip) => c.id === clipId);
  return {
    ...playContext,
    // Use >= 0 check instead of || because 0 is falsy but valid (first song)
    currentIndex: index >= 0 ? index : undefined,
  };
};

/**
 * QueueStore - Manages the music playback queue system
 *
 * ARCHITECTURE OVERVIEW:
 * ====================
 * The queue system consists of three separate queues with priority ordering:
 *
 * 1. MANUAL QUEUE (highest priority) - manualQueue[]
 *    - User-added songs via "Add to Queue"
 *    - Always plays first, in order added
 *    - EPHEMERAL: Songs are removed after playback (can't navigate back)
 *    - Persists across page refreshes
 *
 * 2. CONTEXT QUEUE (medium priority) - contextClips[]
 *    - Songs from current playlist/radio/search/etc.
 *    - Can be shuffled (controlled by contextClipIndexOrder)
 *    - PERMANENT: Forms the playback history (can navigate back/forward)
 *    - Also includes autoplay songs once they've been played
 *
 * 3. AUTOPLAY QUEUE (lowest priority) - autoplayQueue[]
 *    - AI-recommended songs fetched automatically
 *    - Plays when context queue is exhausted
 *    - BECOMES PERMANENT: When played, songs are moved to contextClips
 *
 * STATE TRACKING:
 * ==============
 * - activeQueue: 'manual' | 'context' | null
 *   Which queue is currently playing
 *
 * - contextQueueIndex: number
 *   Current position in context queue (always tracked, even when playing manual songs)
 *
 * - currentManualClip: Clip | null
 *   The manual song currently playing (needed because it's removed from manualQueue)
 *
 * KEY BEHAVIORS:
 * =============
 * 1. Manual queue interrupts context playback:
 *    - When manual song plays, contextQueueIndex stays unchanged
 *    - When manual queue exhausted, resumes from contextQueueIndex + 1
 *    - "Previous" button skips manual songs, returns to saved context position
 *
 * 2. Autoplay songs become permanent:
 *    - When played, moved from autoplayQueue → contextClips
 *    - Can navigate back to them like any context song
 *
 * 3. Shuffle only affects context queue:
 *    - contextClipIndexOrder stores shuffled positions
 *    - Manual and autoplay queues always play in order
 *
 * 4. Playlist operations preserve manual queue:
 *    - Reordering/deleting songs in playlist updates contextClips
 *    - Manual queue continues playing unaffected
 *    - See PlaylistPageClient.tsx updatePlaylistContext() helper
 */
export class QueueStore implements Substore {
  /**
   * Which queue is currently active
   * null means no queue is currently playing
   */
  activeQueue: 'manual' | 'context' | null = null;

  /**
   * Current position in the context queue
   * Always tracks where we are in the context, even when playing manual queue songs
   */
  contextQueueIndex: number = 0;

  /**
   * Context clips - songs from the current playlist/context
   * Only this queue is affected by shuffle mode
   */
  contextClips: Clip[] = [];
  contextClipIndexOrder: number[] = [];
  pendingClips: Clip[] = [];

  contextType: ContextType | null = null;
  contextId: string | null = null;
  contextClipCount: number | null = null;

  /**
   * Manual queue - manually added songs (highest priority)
   * Always plays in order added, unaffected by shuffle
   * Persists across context changes and page refreshes
   */
  manualQueue: QueueItem[] = [];

  /**
   * Currently playing manual queue song
   * Needed because we remove it from manualQueue immediately,
   * but getCurrentClip() needs to return it for the playbar
   */
  currentManualClip: Clip | null = null;

  /**
   * Autoplay queue - staged recommendations (lowest priority)
   * Populated by autoplay recommendations API
   * Plays after manual queue and context queue are exhausted
   */
  autoplayQueue: QueueItem[] = [];

  /**
   * When enabled, the queue plays in a shuffled order
   */
  shuffle: boolean = false;
  /**
   * When enabled, the queue will play the next song after the current one completes
   */
  continuous: boolean = true;
  /**
   * When enabled, we will fetch recommendations to extend the queue near the end
   */
  autoplay: boolean = true;
  /**
   * User's preference for continuous playback
   *
   * When `setPlayContext` is invoked, some contexts will forcibly set
   * `continuous: true`, but others will use this setting instead.
   */
  prefersContinuous: boolean = true;

  currentPlayingSongIsRemoved: boolean = false;

  surfaceType?: string;
  surfaceId?: string;

  readonly root: RootStore;
  get apiClient() {
    return this.root.apiClient;
  }
  // DO NOT ADD INIT LOGIC IN constructor. this will run on every page! considering adding any it in the component init logic instead
  constructor(root: RootStore) {
    this.root = root;
    makeAutoObservableSubstore(this);
  }

  // Initialize is called by rootStore on client-side
  initialize() {
    this.loadFromLocalStorage();
    this.initPersistence();
  }

  toggleShuffle(shuffle = !this.shuffle) {
    const prevShuffle = this.shuffle;
    this.shuffle = shuffle;
    if (shuffle !== prevShuffle) {
      this.generateIndexOrder(shuffle);
    }
  }

  toggleContinuous(continuous = !this.continuous, userPreference = true) {
    this.continuous = continuous;
    if (userPreference) {
      this.prefersContinuous = continuous;
    }
  }

  toggleAutoplay(autoplay = !this.autoplay) {
    this.autoplay = autoplay;
    // Check if we need more clips when we enable autoplay
    if (autoplay) {
      this.autoplayRunwayCheck();
    }
  }

  /**
   * Generates a new clip index order and preserves the current position
   *
   * After shuffling, the song at `contextQueueIndex` will be at index 0.
   * Otherwise, the position will be set to the index of the current song in
   * the natural queue order.
   */
  generateIndexOrder(shuffle = this.shuffle) {
    const newIndexOrder = indexRange(this.contextClips.length, shuffle);
    const currentClipId =
      this.activeQueue === 'context'
        ? this.contextClips[this.contextClipIndexOrder[this.contextQueueIndex]]
            ?.id
        : undefined;
    if (currentClipId != null) {
      const currentClipNewIndex = newIndexOrder.findIndex(
        (i) => this.contextClips[i].id === currentClipId
      );
      if (shuffle) {
        if (currentClipNewIndex >= 0) {
          const firstIndex = newIndexOrder.splice(currentClipNewIndex, 1);
          newIndexOrder.unshift(...firstIndex);
        }
        this.contextQueueIndex = 0;
      } else if (currentClipNewIndex >= 0) {
        this.contextQueueIndex = currentClipNewIndex;
      }
    }

    // If we're playing a manual queue song, move the saved context position to index 0 when shuffling
    if (this.activeQueue === 'manual') {
      const contextClipId =
        this.contextClips[this.contextClipIndexOrder[this.contextQueueIndex]]
          ?.id;
      if (contextClipId) {
        const newPositionForContextClip = newIndexOrder.findIndex(
          (i) => this.contextClips[i].id === contextClipId
        );
        if (newPositionForContextClip >= 0) {
          if (shuffle) {
            // When shuffling, move the context clip to position 0
            const firstIndex = newIndexOrder.splice(
              newPositionForContextClip,
              1
            );
            newIndexOrder.unshift(...firstIndex);
            this.contextQueueIndex = 0;
          } else {
            this.contextQueueIndex = newPositionForContextClip;
          }
        }
      }
    }

    this.contextClipIndexOrder = newIndexOrder;
  }

  // setPlayContext: initialize a new play context
  // Note: manualQueue is intentionally preserved across context changes (user-queued songs)
  // Note: autoplayQueue is cleared on context change (recommendations are context-specific)
  setPlayContext = (playContext: PlayContext, allowAutoplayUpdate = true) => {
    const hasContextChanged = !this.isCurrentContext(
      playContext.contextId,
      playContext.contextType
    );

    this.contextType = playContext.contextType;
    this.contextId = playContext.contextId;
    this.surfaceType = playContext.surfaceType;
    this.surfaceId = playContext.surfaceId;

    // Clear autoplay queue when switching contexts to avoid stale recommendations
    if (hasContextChanged) {
      this.autoplayQueue = [];
    }

    // Set current position for context queue
    if (playContext.currentIndex !== undefined) {
      this.contextQueueIndex = playContext.currentIndex;
      this.activeQueue = 'context';
    } else if (hasContextChanged) {
      this.activeQueue = null;
      this.contextQueueIndex = 0; // Reset index to prevent stale state from previous context
    }

    this.contextClips = playContext.clips || [];
    this.contextClipIndexOrder =
      playContext.clipIndexOrder ||
      indexRange(this.contextClips.length, this.shuffle);
    this.pendingClips = playContext.pendingClips || [];
    this.currentPlayingSongIsRemoved =
      playContext.currentPlayingSongIsRemoved || false;

    // Keep track of the number of non-autoplay clips
    this.contextClipCount =
      this.contextType === ContextType.Song
        ? Math.min(this.contextClips.length, 1)
        : this.contextClips.length;

    // If the context changes, set autoplay based on the context type
    if (allowAutoplayUpdate && hasContextChanged) {
      switch (this.contextType) {
        /**
         * For instances where we don't have a strong opinion, leave the
         * current autoplay setting, assuming that user is happy with it.
         */
        case ContextType.DiscoverCarousel:
        case ContextType.Library:
        case ContextType.Workspace:
        case ContextType.Create:
        case ContextType.CreateClipPreview:
        case ContextType.SongReferencedClip:
        case ContextType.SongExtendedFrom:
        case ContextType.Song:
        case ContextType.Persona:
        case ContextType.PersonaPreview:
          this.continuous = this.prefersContinuous;
          break;
        /**
         * Enable autoplay in contexts where it would be weird not to keep
         * playing by default.
         *
         * That means playlist/album-ish stuff where it's obvious that the
         * songs are part of a collection that the user is playing as a unit
         *
         * We want folks to keep listening, so this is the default behavior.
         * Although we don't have to list the `ContextType` values below explicitly,
         * they're here as a little signpost. Thanks for reading.
         */
        case ContextType.SongRadio:
        case ContextType.Playlist:
        case ContextType.FeaturedFeed:
        case ContextType.Search:
        case ContextType.StyleTag:
        case ContextType.History:
        default:
          this.continuous = true;
          break;
      }
    }
  };

  setClips = (clips: Clip[]) => {
    this.contextClips = clips;
    this.contextClipCount = this.contextClips.length;
    this.generateIndexOrder();
  };

  setPendingClips = (clips: Clip[]) => {
    this.pendingClips = clips;
  };

  findClipIndex = (clipId: string) => {
    return this.contextClipIndexOrder.findIndex(
      (i) => clipId === this.contextClips[i].id
    );
  };

  getClips = () => {
    return this.contextClipIndexOrder.map((i) => this.contextClips[i]) || [];
  };

  // used by the SongPage, which can have pending clips loaded in the background by the MoreSongsPanel
  getClipsForContext = (contextType: ContextType, contextId?: string) => {
    if (
      this.contextType === contextType &&
      (this.contextId === contextId || (!this.contextId && !contextId))
    ) {
      return this.contextClipIndexOrder.map((i) => this.contextClips[i]) || [];
    }
    return this.pendingClips || [];
  };
  // return the next clip in the queue,
  // and update queue state to reflect the updated queue

  // Priority order: manual queue → context queue → autoplay queue
  setToNextClip = () => {
    // Priority 1: Manual queue (highest priority)
    if (this.manualQueue.length > 0) {
      const nextItem = this.manualQueue.shift();
      if (nextItem) {
        this.currentManualClip = nextItem.clip;
        this.activeQueue = 'manual';
        this.root.clips.addClip(nextItem.clip);
        return true;
      }
    }
    this.currentManualClip = null;

    // Priority 2: Context queue
    let nextClipIndex = this.contextQueueIndex;
    if (this.activeQueue === 'manual') {
      nextClipIndex += 1;
    } else if (!this.currentPlayingSongIsRemoved) {
      // Normal progression through context queue
      nextClipIndex += 1;
    }

    // Use shuffled order if shuffle is enabled
    const nextClip = this.shuffle
      ? this.contextClips[this.contextClipIndexOrder[nextClipIndex]]
      : this.contextClips[nextClipIndex];

    if (nextClip) {
      // Only commit the index change if we found a valid clip
      this.contextQueueIndex = nextClipIndex;
      this.activeQueue = 'context';
      return true;
    }

    // Priority 3: Autoplay queue (lowest priority)
    // Add autoplay songs to contextClips permanently so they become part of history
    if (this.autoplayQueue.length > 0) {
      const nextItem = this.autoplayQueue.shift();
      if (nextItem) {
        // Add to the end of context clips
        this.contextClips.push(nextItem.clip);
        this.contextClipIndexOrder.push(this.contextClips.length - 1);
        this.contextQueueIndex = this.contextClips.length - 1;
        this.activeQueue = 'context';
        this.root.clips.addClip(nextItem.clip);
        return true;
      }
    }

    return false;
  };

  setToPreviousClip = () => {
    // Manual queue songs are ephemeral - can't go back to them
    // If we're currently on a manual queue song, skip to the previous context song
    if (this.activeQueue === 'manual' || this.activeQueue === null) {
      if (this.contextClips.length > 0) {
        if (this.contextQueueIndex >= this.contextClips.length) {
          this.contextQueueIndex = Math.max(0, this.contextClips.length - 1);
        }
        this.activeQueue = 'context';
        this.currentManualClip = null;
        return true;
      }
      return false;
    }

    // We're in the context queue (which includes autoplay songs once played) - just decrement the index
    // If we're already at the first song (index 0), there's no previous song
    if (this.contextQueueIndex === 0) {
      return false;
    }

    this.contextQueueIndex = Math.max(0, this.contextQueueIndex - 1);
    return true;
  };

  getCurrentClip = () => {
    if (this.activeQueue === 'manual') {
      return this.currentManualClip;
    }
    if (this.activeQueue === 'context') {
      // Context queue includes original playlist songs + any autoplay songs that have been added
      return this.shuffle
        ? this.contextClips[this.contextClipIndexOrder[this.contextQueueIndex]]
        : this.contextClips[this.contextQueueIndex];
    }
    return undefined;
  };

  /**
   * Check if a specific clip is currently playing from the queue
   */
  isClipPlaying = (clipId: string): boolean => {
    const currentClip = this.getCurrentClip();
    return currentClip?.id === clipId;
  };

  /**
   * Check if currently playing from a specific context position
   * All parameters are optional - only checks the ones provided
   */
  isPlayingAt = (params: {
    contextType?: ContextType;
    contextId?: string;
    index?: number;
    queueType?: 'manual' | 'context';
  }): boolean => {
    if (params.contextType && this.contextType !== params.contextType) {
      return false;
    }
    if (params.contextId && this.contextId !== params.contextId) {
      return false;
    }
    if (params.queueType && this.activeQueue !== params.queueType) {
      return false;
    }
    if (params.index !== undefined && this.contextQueueIndex !== params.index) {
      return false;
    }
    return true;
  };

  addToQueue = (clip: Clip) => {
    // Add to manual queue
    // TODO: Add source context tracking (contextType, contextId) to track where each song came from
    // This requires updating ClipContext to pass down source context from parent components
    this.manualQueue.push({ clip });
  };

  clearManualQueue = () => {
    this.manualQueue = [];
  };

  // TODO: Add individual song remove functionality to UI in future PR
  removeFromManualQueue = (index: number) => {
    if (index >= 0 && index < this.manualQueue.length) {
      this.manualQueue.splice(index, 1);
    }
  };

  playManualQueueSongByIndex = (index: number) => {
    if (index < 0 || index >= this.manualQueue.length) {
      return;
    }

    // Remove all songs before the clicked index (they're skipped)
    this.manualQueue.splice(0, index);

    // Remove from queue and store for getCurrentClip()
    const nextItem = this.manualQueue.shift();
    if (nextItem) {
      this.currentManualClip = nextItem.clip;
      this.activeQueue = 'manual';
      this.root.clips.addClip(nextItem.clip);
    }
  };

  playAutoplayQueueSongByIndex = (index: number) => {
    if (index < 0 || index >= this.autoplayQueue.length) {
      return;
    }

    // Move clicked autoplay song + all before it from autoplayQueue to contextClips
    for (let i = 0; i <= index; i++) {
      const item = this.autoplayQueue[i];
      if (item) {
        this.contextClips.push(item.clip);
        this.contextClipIndexOrder.push(this.contextClips.length - 1);
        this.root.clips.addClip(item.clip);
      }
    }
    this.autoplayQueue.splice(0, index + 1);

    // Set position to the newly added song (bypasses manual queue without clearing it)
    this.contextQueueIndex = this.contextClips.length - 1;
    this.activeQueue = 'context';
  };

  removeFromContextQueue = (index: number) => {
    // Index is relative to contextClipIndexOrder
    if (index >= 0 && index < this.contextClipIndexOrder.length) {
      const clipArrayIndex = this.contextClipIndexOrder[index];
      this.contextClipIndexOrder.splice(index, 1);
      this.contextClipIndexOrder = this.contextClipIndexOrder.map((i) =>
        i > clipArrayIndex ? i - 1 : i
      );
      this.contextClips.splice(clipArrayIndex, 1);

      // Update contextQueueIndex if song removed before current position
      if (index < this.contextQueueIndex) {
        this.contextQueueIndex = Math.max(0, this.contextQueueIndex - 1);
      }

      if (this.contextClipCount !== null && this.contextClipCount > 0) {
        this.contextClipCount--;
      }
    }
  };

  removeFromAutoplayQueue = (index: number) => {
    if (index >= 0 && index < this.autoplayQueue.length) {
      this.autoplayQueue.splice(index, 1);
    }
  };

  setCurrentIndex = (currentIndex: number) => {
    this.contextQueueIndex = currentIndex;
    this.activeQueue = 'context';
  };

  isCurrentContext = (contextId?: string, contextType?: string) =>
    !!contextId &&
    this.contextType === contextType &&
    this.contextId === contextId;

  isPlaylistCurrentContext = (playlistId?: string) =>
    !!playlistId &&
    (this.contextType === ContextType.Playlist ||
      this.contextType === ContextType.StyleTag ||
      this.contextType === ContextType.Search ||
      this.contextType === ContextType.History ||
      this.contextType === ContextType.Shortcut) &&
    this.contextId === playlistId;

  setCurrentPlayingSongIsRemoved = (currentPlayingSongIsRemoved: boolean) => {
    this.currentPlayingSongIsRemoved = currentPlayingSongIsRemoved;
  };

  reorderClips(sourceIndex: number, destinationIndex: number) {
    if (this.shuffle) {
      // In shuffle mode, reordering the clips just swaps the indexes
      const result = [...this.contextClipIndexOrder];
      const [removed] = result.splice(sourceIndex, 1);
      result.splice(destinationIndex, 0, removed);
      this.contextClipIndexOrder = result;
    } else {
      // Otherwise, reordering the clips directly mutates the clips array
      const result = [...this.contextClips];
      const [removed] = result.splice(sourceIndex, 1);
      result.splice(destinationIndex, 0, removed);
      this.contextClips = result;
      this.contextClipIndexOrder = indexRange(this.contextClips.length, false);
    }
  }

  async autoplayRunwayCheck() {
    // If the flag is disabled, don't bother
    if (!this.root.session.flags?.['queue-auto-refresh']) return;
    // If autoplay is disabled, don't bother
    if (!this.autoplay) return;
    // If we are at the end of the queue, fetch more clips
    const remainingContextClips =
      this.contextClips.length - this.contextQueueIndex - 1;
    // Include autoplay queue in the count
    const totalRemaining = remainingContextClips + this.autoplayQueue.length;
    if (totalRemaining <= MIN_QUEUE_ITEMS_REMAINING_BEFORE_REFRESH) {
      await this.fetchMoreClipsForQueue();
    }
  }

  async fetchMoreClipsForQueue() {
    // Get 50 latest clips in queue to prevent POST request from being too large.
    const mostRecentClipsInQueue = this.contextClipIndexOrder.slice(
      -NUM_MOST_RECENT_CLIPS_IN_QUEUE
    );
    const currentClip = this.getCurrentClip();
    const { data } = await this.apiClient.POST('/api/clips/autoplay/', {
      body: {
        clip_id: currentClip?.id || '',
        playlist_id: '',
        query: '',
        clips_in_queue: mostRecentClipsInQueue.map((i) => ({
          clip_id: this.contextClips[i].id,
        })),
        page_size: 30,
      },
    });
    if (!data) {
      return;
    }
    const clipsToAutoplay = data.clips;
    clipsToAutoplay.forEach((clip) => {
      // Add to autoplay queue instead of directly to clips array
      // TODO: Add recId and recMetadata when backend provides them
      this.autoplayQueue.push({ clip });
      this.root.clips.addClip(clip);
    });
  }

  // NOTE: We currently use localStorage for client-side persistence only.
  // TODO: Add persistQueue() method to save queue to database for cross-device sync
  // This would allow mobile clients to restore queue state when switching devices
  // between mobile/web. Should store: manualQueue, autoplayQueue, currentIndex,
  // contextType, contextId, and possibly shuffle/continuous preferences.

  initPersistence = () => {
    // Auto-save to localStorage when queue changes
    reaction(
      () => ({
        manualQueue: this.manualQueue.slice(),
        autoplayQueue: this.autoplayQueue.slice(),
      }),
      () => {
        if (this.root.isLocalStorageAvailable) {
          try {
            // Expensive serialization only happens here, when actually saving
            const data = {
              manualQueue: this.manualQueue.map((item) => ({
                clip: JSON.parse(JSON.stringify(item.clip)),
              })),
              autoplayQueue: this.autoplayQueue.map((item) => ({
                clip: JSON.parse(JSON.stringify(item.clip)),
                recId: item.recId,
                recMetadata: item.recMetadata,
              })),
            };
            const storageStr = JSON.stringify(data);
            localStorage.setItem(QUEUE_STORAGE_KEY, storageStr);
          } catch (e) {
            console.error('Failed to save queue state:', e);
          }
        }
      }
    );
  };

  loadFromLocalStorage = () => {
    if (this.root.isLocalStorageAvailable) {
      try {
        const storageStr = localStorage.getItem(QUEUE_STORAGE_KEY);
        if (storageStr) {
          const stateJson = JSON.parse(storageStr);

          // Validate the structure of loaded data
          if (!stateJson || typeof stateJson !== 'object') {
            console.warn(
              'Invalid queue state structure in localStorage, clearing'
            );
            localStorage.removeItem(QUEUE_STORAGE_KEY);
            return;
          }

          runInAction(() => {
            // Load clips from localStorage and add to clipStore for MobX observability
            if (stateJson.manualQueue && Array.isArray(stateJson.manualQueue)) {
              this.manualQueue = (
                stateJson.manualQueue as Array<{ clip: Clip }>
              )
                .map((item) => {
                  // Add clip to clipStore to make it observable
                  this.root.clips.addClip(item.clip);
                  return { clip: item.clip };
                })
                .filter((item): item is QueueItem => item?.clip != null);
            }
            if (
              stateJson.autoplayQueue &&
              Array.isArray(stateJson.autoplayQueue)
            ) {
              this.autoplayQueue = (stateJson.autoplayQueue as any[])
                .map((item) => {
                  if (!item?.clip) return null;
                  // Add clip to clipStore to make it observable
                  this.root.clips.addClip(item.clip);
                  return {
                    clip: item.clip,
                    recId: item.recId,
                    recMetadata: item.recMetadata,
                  } as QueueItem;
                })
                .filter((item): item is QueueItem => item !== null);
            }
          });
        }
      } catch (e) {
        console.error('Failed to load queue state:', e);
        // Clear corrupted localStorage to prevent repeated errors on page load
        try {
          localStorage.removeItem(QUEUE_STORAGE_KEY);
        } catch (clearError) {
          console.error('Failed to clear corrupted queue state:', clearError);
        }
      }
    }
  };
}
