// MusicRoom - Track Container
// Manages track lists with selection, drag-and-drop, and context menus

(function() {
  const M = window.MusicRoom;

  // Track if a drag is in progress (to prevent re-renders from canceling it)
  let isDragging = false;

  // Selection state per container type
  const selection = {
    queue: new Set(),      // indices
    library: new Set(),    // track IDs
    playlist: new Set()    // indices (for duplicate support)
  };

  // Last selected for shift-select
  const lastSelected = {
    queue: null,
    library: null,
    playlist: null
  };

  // Drag state (shared across containers)
  let dragSource = null;
  let draggedIndices = [];
  let draggedTrackIds = [];
  let dropTargetIndex = null;

  // Active context menu
  let activeContextMenu = null;

  /**
   * Create a track container manager
   * @param {Object} config
   * @param {string} config.type - 'queue' | 'library' | 'playlist'
   * @param {HTMLElement} config.element - Container DOM element
   * @param {Function} config.getTracks - Returns array of tracks to render
   * @param {Function} [config.getFilteredTracks] - Returns filtered tracks (for library search)
   * @param {boolean} [config.canEditQueue] - Whether user can modify queue
   * @param {boolean} [config.canReorder] - Whether tracks can be reordered (queue only)
   * @param {boolean} [config.isPlaylistOwner] - Whether user owns the playlist (can remove/reorder)
   * @param {string} [config.playlistId] - Playlist ID (for playlist type)
   * @param {Function} [config.onRender] - Callback after render
   */
  function createContainer(config) {
    const {
      type,
      element,
      getTracks,
      getFilteredTracks,
      canReorder = false,
      isPlaylistOwner = false,
      playlistId = null,
      onRender
    } = config;

    let currentTracks = [];

    // Get canEditQueue dynamically (permissions may change)
    const getCanEditQueue = () => config.canEditQueue ?? M.canControl();

    // Track if this container needs a render after drag ends
    let pendingRender = false;

    function render() {
      // Defer render if a drag is in progress (would cancel the drag)
      if (isDragging) {
        pendingRender = true;
        return;
      }
      pendingRender = false;
      const canEditQueue = getCanEditQueue();
      element.innerHTML = "";
      
      // Get tracks (filtered for library, direct for queue/playlist)
      currentTracks = getFilteredTracks ? getFilteredTracks() : getTracks();
      
      // Always wire up container drop handlers first (even for empty containers)
      if (canEditQueue && type === 'queue') {
        wireQueueContainerDrop(element);
      }
      if (isPlaylistOwner && type === 'playlist' && playlistId) {
        wirePlaylistContainerDrop(element);
      }
      
      if (currentTracks.length === 0) {
        const emptyMsg = type === 'queue' ? 'Queue empty - drag tracks here' 
          : type === 'library' ? 'No tracks' 
          : 'No tracks - drag here to add';
        element.innerHTML = `<div class="empty">${emptyMsg}</div>`;
        if (onRender) onRender();
        return;
      }

      currentTracks.forEach((item, filteredIndex) => {
        // item can be { track, originalIndex } or just track
        const track = item.track || item;
        const index = type === 'queue' ? filteredIndex : (item.originalIndex ?? filteredIndex);
        const trackId = track.id || track.filename;
        
        // Queue and playlist use indices, library uses trackIds
        const isSelected = type === 'library' 
          ? selection.library.has(trackId)
          : selection[type].has(index);
        
        const isCached = M.cachedTracks.has(trackId);
        const isActive = type === 'queue' && index === M.currentIndex;
        
        // Library/playlist always draggable (read access), queue needs edit permission
        const isDraggable = type === 'library' || type === 'playlist' || (type === 'queue' && canEditQueue);
        
        const div = M.trackComponent.render(track, {
          view: type,
          index: filteredIndex,
          displayIndex: type === 'queue' || type === 'playlist' ? filteredIndex + 1 : null,
          isSelected,
          isCached,
          isActive,
          draggable: isDraggable
        });

        // Wire up event handlers
        wireTrackEvents(div, track, filteredIndex, index, canEditQueue);
        
        element.appendChild(div);
      });

      if (onRender) onRender();
    }

    function wirePlaylistContainerDrop(container) {
      container.ondragover = (e) => {
        if (dragSource === 'queue' || dragSource === 'library' || dragSource === 'playlist') {
          e.preventDefault();
          e.dataTransfer.dropEffect = dragSource === 'playlist' ? "move" : "copy";
          container.classList.add("drop-target");
        }
      };

      container.ondragleave = (e) => {
        if (!container.contains(e.relatedTarget)) {
          container.classList.remove("drop-target");
        }
      };

      container.ondrop = (e) => {
        container.classList.remove("drop-target");
        // Clear any drop indicators on tracks
        container.querySelectorAll(".drop-above, .drop-below").forEach(el => {
          el.classList.remove("drop-above", "drop-below");
        });
        
        if (draggedTrackIds.length > 0) {
          e.preventDefault();
          
          // If dropTargetIndex was set by a track, use that position
          // Otherwise append to end
          const targetPos = dropTargetIndex !== null ? dropTargetIndex : currentTracks.length;
          
          if (dragSource === 'playlist') {
            reorderPlaylist(draggedIndices, targetPos);
          } else if (dragSource === 'queue' || dragSource === 'library') {
            if (currentTracks.length === 0) {
              addTracksToPlaylist(draggedTrackIds);
            } else {
              addTracksToPlaylistAt(draggedTrackIds, targetPos);
            }
          }
          
          draggedTrackIds = [];
          draggedIndices = [];
          dragSource = null;
          dropTargetIndex = null;
        }
      };
    }

    async function addTracksToPlaylist(trackIds) {
      if (!playlistId) return;
      
      const res = await fetch(`/api/playlists/${playlistId}/tracks`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ add: trackIds })
      });
      
      if (res.ok) {
        M.showToast(`Added ${trackIds.length} track${trackIds.length > 1 ? 's' : ''} to playlist`);
        if (M.playlists) M.playlists.reloadCurrentPlaylist();
      } else {
        M.showToast("Failed to add to playlist", "error");
      }
    }

    async function addTracksToPlaylistAt(trackIds, position) {
      if (!playlistId) return;
      
      // Get current tracks and insert at position
      const current = currentTracks.map(t => (t.track || t).id);
      const newList = [...current.slice(0, position), ...trackIds, ...current.slice(position)];
      
      const res = await fetch(`/api/playlists/${playlistId}/tracks`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ set: newList })
      });
      
      if (res.ok) {
        M.showToast(`Added ${trackIds.length} track${trackIds.length > 1 ? 's' : ''}`);
        if (M.playlists) M.playlists.reloadCurrentPlaylist();
      } else {
        M.showToast("Failed to add to playlist", "error");
      }
    }

    async function reorderPlaylist(indices, targetIndex) {
      if (!playlistId) return;
      
      // Get current track IDs
      const current = currentTracks.map(t => (t.track || t).id);
      
      // Sort indices descending for safe removal
      const sortedIndices = [...indices].sort((a, b) => b - a);
      
      // Get track IDs being moved
      const movedTrackIds = indices.map(i => current[i]);
      
      // Calculate insertion position (adjusted for removed items before target)
      let insertAt = targetIndex;
      for (const idx of indices) {
        if (idx < targetIndex) {
          insertAt--;
        }
      }
      
      // Remove tracks at indices (from end to preserve indices)
      const remaining = current.filter((_, i) => !indices.includes(i));
      
      insertAt = Math.max(0, Math.min(insertAt, remaining.length));
      
      // Insert at new position
      const newList = [...remaining.slice(0, insertAt), ...movedTrackIds, ...remaining.slice(insertAt)];
      
      const res = await fetch(`/api/playlists/${playlistId}/tracks`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ set: newList })
      });
      
      if (res.ok) {
        selection.playlist.clear();
        lastSelected.playlist = null;
        if (M.playlists) M.playlists.reloadCurrentPlaylist();
      } else {
        M.showToast("Failed to reorder playlist", "error");
      }
    }

    function wireTrackEvents(div, track, filteredIndex, originalIndex, canEditQueue) {
      const trackId = track.id || track.filename;
      const index = type === 'queue' ? originalIndex : filteredIndex;

      // Click/double-click handling - delay render to allow double-click detection
      let clickTimeout = null;
      div.onclick = (e) => {
        if (e.target.closest('.track-actions')) return;
        toggleSelection(index, trackId, e.shiftKey, e.ctrlKey || e.metaKey);
        clearTimeout(clickTimeout);
        clickTimeout = setTimeout(() => render(), 200);
      };

      // Double-click - queue: jump to track, library/playlist: add to queue
      div.ondblclick = (e) => {
        if (e.target.closest('.track-actions')) return;
        clearTimeout(clickTimeout);
        if (type === 'queue') {
          M.jumpToTrack(originalIndex);
        } else {
          addToQueue([trackId]);
        }
      };

      // Context menu
      div.oncontextmenu = (e) => {
        e.preventDefault();
        showContextMenu(e, track, originalIndex, canEditQueue);
      };

      const menuBtn = div.querySelector(".track-menu-btn");
      if (menuBtn) {
        menuBtn.onclick = (e) => {
          e.stopPropagation();
          const rect = menuBtn.getBoundingClientRect();
          showContextMenu({
            preventDefault() {},
            clientX: Math.min(rect.right, window.innerWidth - 8),
            clientY: Math.min(rect.bottom, window.innerHeight - 8)
          }, track, originalIndex, canEditQueue);
        };
      }

      // Drag start/end handlers - library/playlist always (read access), queue needs edit permission
      const canDrag = type === 'library' || type === 'playlist' || (type === 'queue' && canEditQueue);
      if (canDrag) {
        div.ondragstart = (e) => handleDragStart(e, track, originalIndex, div);
        div.ondragend = (e) => handleDragEnd(e, div);
      }
      
      // Drop handlers - queue and playlist accept drops
      if (canEditQueue && type === 'queue') {
        div.ondragover = (e) => handleDragOver(e, div, originalIndex);
        div.ondragleave = (e) => handleDragLeave(e, div);
        div.ondrop = (e) => handleDrop(e, div, originalIndex);
      }
      
      if (type === 'playlist' && playlistId && isPlaylistOwner) {
        div.ondragover = (e) => handleDragOver(e, div, filteredIndex);
        div.ondragleave = (e) => handleDragLeave(e, div);
        div.ondrop = (e) => handleDrop(e, div, filteredIndex);
      }
    }

    function toggleSelection(index, trackId, shiftKey, ctrlKey) {
      // Queue and playlist use indices, library uses trackIds
      const key = type === 'library' ? trackId : index;
      const sel = selection[type];
      const currentIdx = type === 'library' ? getFilteredIndex(trackId) : index;

      if (shiftKey && lastSelected[type] !== null) {
        // Shift+click: Range select (add to existing selection)
        const start = Math.min(lastSelected[type], currentIdx);
        const end = Math.max(lastSelected[type], currentIdx);
        
        for (let i = start; i <= end; i++) {
          if (type === 'library') {
            const t = currentTracks[i];
            if (t) {
              const id = (t.track || t).id;
              if (id) sel.add(id);
            }
          } else {
            // Queue and playlist use indices
            sel.add(i);
          }
        }
      } else if (ctrlKey) {
        // Ctrl+click: Toggle single item
        if (sel.has(key)) {
          sel.delete(key);
        } else {
          sel.add(key);
        }
        lastSelected[type] = currentIdx;
      } else {
        // Plain click: Select only this item (clear others)
        sel.clear();
        sel.add(key);
        lastSelected[type] = currentIdx;
      }
    }

    function getFilteredIndex(trackId) {
      return currentTracks.findIndex(t => ((t.track || t).id) === trackId);
    }

    function handleDragStart(e, track, index, div) {
      console.log(`[Drag] handleDragStart: type=${type} index=${index} track=${track.title || track.filename}`);
      isDragging = true;
      const trackId = track.id || track.filename;
      dragSource = type;
      
      if (type === 'queue') {
        draggedIndices = selection.queue.has(index) ? [...selection.queue] : [index];
        draggedTrackIds = draggedIndices.map(i => M.queue[i]?.id).filter(Boolean);
      } else if (type === 'playlist') {
        // Playlist uses indices for selection (supports duplicates)
        draggedIndices = selection.playlist.has(index) ? [...selection.playlist] : [index];
        draggedTrackIds = draggedIndices.map(i => {
          const t = currentTracks[i];
          return t ? (t.track || t).id : null;
        }).filter(Boolean);
      } else {
        // Library uses trackIds
        draggedTrackIds = selection.library.has(trackId) ? [...selection.library] : [trackId];
        draggedIndices = [];
      }
      
      div.classList.add("dragging");
      // Use "copyMove" to allow both copy and move operations
      e.dataTransfer.effectAllowed = "copyMove";
      e.dataTransfer.setData("text/plain", `${type}:${draggedTrackIds.join(",")}`);
    }

    function handleDragEnd(e, div) {
      isDragging = false;
      div.classList.remove("dragging");
      draggedIndices = [];
      draggedTrackIds = [];
      dragSource = null;
      dropTargetIndex = null;
      
      // Clear all drop indicators
      element.querySelectorAll(".drop-above, .drop-below").forEach(el => {
        el.classList.remove("drop-above", "drop-below");
      });
      
      // Execute deferred render if any
      if (pendingRender) {
        setTimeout(() => render(), 50);
      }
    }

    function handleDragOver(e, div, index) {
      e.preventDefault();
      
      // Set drop effect based on source
      if (type === 'queue') {
        e.dataTransfer.dropEffect = dragSource === 'queue' ? "move" : "copy";
      } else if (type === 'playlist') {
        e.dataTransfer.dropEffect = dragSource === 'playlist' ? "move" : "copy";
      }
      
      const rect = div.getBoundingClientRect();
      const midY = rect.top + rect.height / 2;
      const isAbove = e.clientY < midY;
      
      // Clear other indicators
      element.querySelectorAll(".drop-above, .drop-below").forEach(el => {
        el.classList.remove("drop-above", "drop-below");
      });
      
      // Don't show indicator on dragged items
      if (dragSource === type && draggedIndices.includes(index)) return;
      
      div.classList.add(isAbove ? "drop-above" : "drop-below");
      dropTargetIndex = isAbove ? index : index + 1;
    }

    function handleDragLeave(e, div) {
      div.classList.remove("drop-above", "drop-below");
    }

    function handleDrop(e, div, index) {
      console.log(`[Drag] handleDrop: type=${type} index=${index} dropTargetIndex=${dropTargetIndex} dragSource=${dragSource} draggedIndices=${draggedIndices}`);
      e.preventDefault();
      e.stopPropagation();
      div.classList.remove("drop-above", "drop-below");
      element.classList.remove("drop-target");
      
      if (dropTargetIndex === null) {
        console.log(`[Drag] handleDrop: dropTargetIndex is null, aborting`);
        return;
      }

      if (type === 'queue') {
        if (dragSource === 'queue' && draggedIndices.length > 0) {
          // Reorder within queue
          const minDragged = Math.min(...draggedIndices);
          const maxDragged = Math.max(...draggedIndices);
          console.log(`[Drag] Reorder check: dropTargetIndex=${dropTargetIndex} minDragged=${minDragged} maxDragged=${maxDragged}`);
          if (dropTargetIndex < minDragged || dropTargetIndex > maxDragged + 1) {
            console.log(`[Drag] Calling reorderQueue(${draggedIndices}, ${dropTargetIndex})`);
            reorderQueue(draggedIndices, dropTargetIndex);
          } else {
            console.log(`[Drag] Skipping reorder - dropping on self`);
          }
        } else if ((dragSource === 'library' || dragSource === 'playlist') && draggedTrackIds.length > 0) {
          // Insert tracks from library or playlist
          insertTracksAtPosition(draggedTrackIds, dropTargetIndex);
        }
      } else if (type === 'playlist') {
        if (dragSource === 'playlist' && draggedIndices.length > 0) {
          // Reorder within playlist
          reorderPlaylist(draggedIndices, dropTargetIndex);
        } else if ((dragSource === 'queue' || dragSource === 'library') && draggedTrackIds.length > 0) {
          // Insert at position
          addTracksToPlaylistAt(draggedTrackIds, dropTargetIndex);
        }
      }

      draggedIndices = [];
      draggedTrackIds = [];
      dragSource = null;
      dropTargetIndex = null;
    }

    function wireQueueContainerDrop(container) {
      container.ondragover = (e) => {
        if (dragSource === 'library' || dragSource === 'playlist') {
          e.preventDefault();
          e.dataTransfer.dropEffect = "copy";
          if (M.queue.length === 0) {
            container.classList.add("drop-target");
          }
        }
      };

      container.ondragleave = (e) => {
        if (!container.contains(e.relatedTarget)) {
          container.classList.remove("drop-target");
        }
      };

      container.ondrop = (e) => {
        container.classList.remove("drop-target");
        if ((dragSource === 'library' || dragSource === 'playlist') && draggedTrackIds.length > 0) {
          e.preventDefault();
          const targetIndex = dropTargetIndex !== null ? dropTargetIndex : M.queue.length;
          insertTracksAtPosition(draggedTrackIds, targetIndex);
          draggedTrackIds = [];
          dragSource = null;
          dropTargetIndex = null;
        }
      };
    }

    async function reorderQueue(indices, targetIndex) {
      if (!M.currentChannelId) return;
      
      const res = await fetch("/api/channels/" + M.currentChannelId + "/queue", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ move: indices, to: targetIndex })
      });
      
      if (res.status === 403) M.flashPermissionDenied();
      else if (res.ok) {
        selection.queue.clear();
        lastSelected.queue = null;
      } else {
        M.showToast("Failed to reorder queue", "error");
      }
    }

    async function insertTracksAtPosition(trackIds, position) {
      if (!M.currentChannelId) return;
      
      const res = await fetch("/api/channels/" + M.currentChannelId + "/queue", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ add: trackIds, insertAt: position })
      });
      
      if (res.status === 403) M.flashPermissionDenied();
      else if (res.ok) {
        M.showToast(`Added ${trackIds.length} track${trackIds.length > 1 ? 's' : ''}`);
        clearSelection();
      } else {
        M.showToast("Failed to add tracks", "error");
      }
    }

    async function playTrack(track, index) {
      const trackId = track.id || track.filename;
      const title = track.title?.trim() || (track.filename || trackId || "Unknown").replace(/\.[^.]+$/, "");
      
      if (type === 'queue') {
        // Jump to track in queue
        if (M.synced && M.currentChannelId) {
          if (!M.canControl()) {
            M.flashPermissionDenied();
            M.showToast("Sign in to control playback", "warning", 2500);
            return;
          }
          const res = await fetch("/api/channels/" + M.currentChannelId + "/jump", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ index })
          });
          if (res.status === 403) M.flashPermissionDenied();
        } else {
          // Local playback
          M.currentIndex = index;
          M.currentTrackId = trackId;
          M.serverTrackDuration = track.duration;
          M.setTrackTitle(title);
          M.loadingSegments.clear();
          const cachedUrl = await M.loadTrackBlob(trackId);
          M.audio.src = cachedUrl || M.getTrackUrl(trackId);
          M.audio.currentTime = 0;
          M.localTimestamp = 0;
          M.audio.play();
          render();
        }
      }
    }

    async function previewTrack(track) {
      const trackId = track.id || track.filename;
      const title = track.title?.trim() || (track.filename || trackId || "Unknown").replace(/\.[^.]+$/, "");
      
      M.currentTrackId = trackId;
      M.serverTrackDuration = track.duration;
      M.setTrackTitle(title);
      M.loadingSegments.clear();
      
      const cachedUrl = await M.loadTrackBlob(trackId);
      M.audio.src = cachedUrl || M.getTrackUrl(trackId);
      M.audio.currentTime = 0;
      M.localTimestamp = 0;
      M.audio.play();
      
      // Desync and disable auto-resync
      if (M.synced || M.wantSync) {
        M.synced = false;
        M.wantSync = false;
        M.showToast("Previewing track (desynced)");
        M.updateUI();
      }
    }

    function showContextMenu(e, track, index, canEditQueue) {
      const trackId = track.id || track.filename;
      const title = track.title?.trim() || (track.filename || trackId || "Unknown").replace(/\.[^.]+$/, "");
      
      const sel = selection[type];
      const hasSelection = sel.size > 0;
      const selectedCount = hasSelection ? sel.size : 1;
      
      // Get IDs/indices for bulk operations
      let idsForAction, indicesToRemove;
      if (type === 'queue') {
        indicesToRemove = hasSelection ? [...sel] : [index];
        idsForAction = indicesToRemove.map(i => M.queue[i]?.id).filter(Boolean);
      } else if (type === 'playlist') {
        // Playlist uses indices for selection/removal (supports duplicates)
        indicesToRemove = hasSelection ? [...sel] : [index];
        idsForAction = indicesToRemove.map(i => currentTracks[i]?.track?.id || currentTracks[i]?.id).filter(Boolean);
      } else {
        // Library uses trackIds
        idsForAction = hasSelection ? [...sel] : [trackId];
      }

      const menuItems = [];

      // Play (queue only, single track or single selection)
      if (type === 'queue' && selectedCount === 1 && (canEditQueue || !M.synced)) {
        menuItems.push({
          label: "▶ Play",
          action: () => playTrack(track, index)
        });
      }

      // Preview (all views, single track or single selection)
      if (selectedCount === 1) {
        menuItems.push({
          label: "⏵ Preview",
          action: () => previewTrack(track)
        });
      }

      // Queue actions
      if (type === 'queue' && canEditQueue) {
        menuItems.push({
          label: selectedCount > 1 ? `＋ Add ${selectedCount} again` : "＋ Add again",
          action: () => addToQueue(idsForAction)
        });
        menuItems.push({
          label: selectedCount > 1 ? `⏭ Play ${selectedCount} next` : "⏭ Play next",
          action: () => addToQueue(idsForAction, true)
        });
        menuItems.push({
          label: selectedCount > 1 ? `✕ Remove ${selectedCount}` : "✕ Remove",
          danger: true,
          action: () => removeFromQueue(indicesToRemove)
        });
      }

      // Library actions - can add to queue if user has queue edit permission
      if (type === 'library' && canEditQueue) {
        menuItems.push({
          label: selectedCount > 1 ? `＋ Add ${selectedCount} to queue` : "＋ Add to queue",
          action: () => addToQueue(idsForAction)
        });
        menuItems.push({
          label: selectedCount > 1 ? `⏭ Play ${selectedCount} next` : "⏭ Play next",
          action: () => addToQueue(idsForAction, true)
        });
      }

      // Playlist actions
      if (type === 'playlist') {
        // Can add to queue if user has queue edit permission
        if (canEditQueue) {
          menuItems.push({
            label: selectedCount > 1 ? `＋ Add ${selectedCount} to queue` : "＋ Add to queue",
            action: () => addToQueue(idsForAction)
          });
          menuItems.push({
            label: selectedCount > 1 ? `⏭ Play ${selectedCount} next` : "⏭ Play next",
            action: () => addToQueue(idsForAction, true)
          });
        }
        // Can remove if user owns the playlist
        if (isPlaylistOwner && playlistId) {
          menuItems.push({
            label: selectedCount > 1 ? `🗑️ Remove ${selectedCount}` : "🗑️ Remove",
            danger: true,
            action: () => removeFromPlaylist(indicesToRemove)
          });
        }
      }

      // Preload (library/queue, non-stream mode)
      if ((type === 'library' || type === 'queue') && !M.streamOnly) {
        menuItems.push({
          label: selectedCount > 1 ? `Preload ${selectedCount}` : "Preload",
          action: () => {
            const uncached = idsForAction.filter(id => !M.cachedTracks.has(id));
            if (uncached.length === 0) {
              M.showToast("Already cached");
              return;
            }
            M.showToast(`Preloading ${uncached.length}...`);
            uncached.forEach(id => M.downloadAndCacheTrack(id));
          }
        });
      }

      // Add to Playlist
      if (M.playlists && !M.currentUser?.isGuest) {
        const submenu = M.playlists.showAddToPlaylistMenu(idsForAction);
        if (submenu && submenu.length > 0) {
          menuItems.push({
            label: idsForAction.length > 1 ? `📁 Add ${idsForAction.length} to Playlist...` : "📁 Add to Playlist...",
            submenu
          });
        }
      }

      // Copy link (single)
      if (!hasSelection) {
        menuItems.push({
          label: "🔗 Copy link",
          action: () => {
            navigator.clipboard.writeText(`${location.origin}/listen/${encodeURIComponent(trackId)}`);
            M.showToast("Link copied");
          }
        });
      }

      // Clear selection
      if (hasSelection) {
        menuItems.push({
          label: "Clear selection",
          action: () => { clearSelection(); render(); }
        });
      }

      M.contextMenu.show(e, menuItems);
    }

    async function addToQueue(trackIds, playNext = false) {
      if (!M.currentChannelId) {
        M.showToast("No channel selected");
        return;
      }
      const body = playNext 
        ? { add: trackIds, insertAt: (M.currentIndex ?? 0) + 1 }
        : { add: trackIds };
      
      const res = await fetch("/api/channels/" + M.currentChannelId + "/queue", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body)
      });
      
      if (res.status === 403) M.flashPermissionDenied();
      else if (res.ok) {
        M.showToast(playNext ? "Playing next" : "Added to queue");
        clearSelection();
        render();
      } else {
        M.showToast("Failed to add to queue", "error");
      }
    }

    async function removeFromQueue(indices) {
      if (!M.currentChannelId) return;
      
      const res = await fetch("/api/channels/" + M.currentChannelId + "/queue", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ remove: indices })
      });
      
      if (res.status === 403) M.flashPermissionDenied();
      else if (res.ok) {
        M.showToast("Removed");
        clearSelection();
        render();
      } else {
        M.showToast("Failed to remove from queue", "error");
      }
    }

    async function removeFromPlaylist(indices) {
      if (!playlistId) return;
      
      const res = await fetch(`/api/playlists/${playlistId}/tracks`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ remove: indices })
      });
      
      if (res.ok) {
        M.showToast("Removed from playlist");
        clearSelection();
        if (M.playlists) M.playlists.reloadCurrentPlaylist();
      } else {
        M.showToast("Failed to remove from playlist", "error");
      }
    }

    function clearSelection() {
      selection[type].clear();
      lastSelected[type] = null;
    }

    function getSelection() {
      return [...selection[type]];
    }

    return {
      render,
      clearSelection,
      getSelection,
      get currentTracks() { return currentTracks; }
    };
  }

  // Context menu rendering (shared)
  function showContextMenuUI(e, items) {
    e.preventDefault();
    hideContextMenu();
    
    const menu = document.createElement("div");
    menu.className = "context-menu";
    
    items.forEach(item => {
      if (item.separator) {
        const sep = document.createElement("div");
        sep.className = "context-menu-separator";
        menu.appendChild(sep);
        return;
      }
      
      const el = document.createElement("div");
      el.className = "context-menu-item" + (item.danger ? " danger" : "") + (item.disabled ? " disabled" : "");
      el.textContent = item.label;
      
      if (item.submenu) {
        el.classList.add("has-submenu");
        el.innerHTML += ' ▸';
        
        const sub = document.createElement("div");
        sub.className = "context-submenu";
        const subInner = document.createElement("div");
        subInner.className = "context-submenu-inner";
        item.submenu.forEach(subItem => {
          const subEl = document.createElement("div");
          subEl.className = "context-menu-item";
          subEl.textContent = subItem.label;
          subEl.onclick = (e) => {
            e.stopPropagation();
            hideContextMenu();
            subItem.action();
          };
          subInner.appendChild(subEl);
        });
        sub.appendChild(subInner);
        el.appendChild(sub);
      } else if (!item.disabled) {
        el.onclick = () => {
          hideContextMenu();
          item.action();
        };
      }
      
      menu.appendChild(el);
    });
    
    menu.style.left = e.clientX + "px";
    menu.style.top = e.clientY + "px";
    document.body.appendChild(menu);
    
    // Adjust if off-screen
    const rect = menu.getBoundingClientRect();
    if (rect.right > window.innerWidth) {
      menu.style.left = Math.max(5, window.innerWidth - rect.width - 5) + "px";
    }
    if (rect.bottom > window.innerHeight) {
      menu.style.top = Math.max(5, window.innerHeight - rect.height - 5) + "px";
    }
    
    activeContextMenu = menu;
    
    // Close on click outside
    setTimeout(() => {
      document.addEventListener("click", hideContextMenu, { once: true });
    }, 0);
  }

  function hideContextMenu() {
    if (activeContextMenu) {
      activeContextMenu.remove();
      activeContextMenu = null;
    }
  }

  // Export
  M.trackContainer = { createContainer };
  M.contextMenu = {
    show: showContextMenuUI,
    hide: hideContextMenu
  };

  // Clear all selections helper
  M.clearAllSelections = function() {
    Object.keys(selection).forEach(k => {
      selection[k].clear();
      lastSelected[k] = null;
    });
  };
})();
