// MusicRoom - Audio Cache module
// Track caching, downloading, and prefetching

(function() {
  const M = window.MusicRoom;
  
  // Get or create cache for a track
  M.getTrackCache = function(trackId) {
    if (!trackId) return new Set();
    if (!M.trackCaches.has(trackId)) {
      M.trackCaches.set(trackId, new Set());
    }
    return M.trackCaches.get(trackId);
  };
  
  // Get track URL - prefers cached blob, falls back to API
  M.getTrackUrl = function(trackId) {
    return M.trackBlobs.get(trackId) || "/api/tracks/" + encodeURIComponent(trackId);
  };
  
  // Load a track blob from storage or fetch from server
  M.loadTrackBlob = async function(trackId) {
    // Check if already in memory
    if (M.trackBlobs.has(trackId)) {
      return M.trackBlobs.get(trackId);
    }
    
    // Check persistent storage
    const cached = await TrackStorage.get(trackId);
    if (cached) {
      const blobUrl = URL.createObjectURL(cached.blob);
      M.trackBlobs.set(trackId, blobUrl);
      // Mark all segments as cached
      const trackCache = M.getTrackCache(trackId);
      for (let i = 0; i < M.SEGMENTS; i++) trackCache.add(i);
      M.bulkDownloadStarted.set(trackId, true);
      // Update cache status indicator
      if (!M.cachedTracks.has(trackId)) {
        M.cachedTracks.add(trackId);
        M.renderQueue();
        M.renderLibrary();
      }
      return blobUrl;
    }
    
    return null;
  };
  
  // Check if track has all segments and trigger full cache if so
  M.checkAndCacheComplete = function(trackId) {
    if (!trackId || M.cachedTracks.has(trackId)) return;
    
    const trackCache = M.trackCaches.get(trackId);
    if (trackCache && trackCache.size >= M.SEGMENTS) {
      console.log("[Cache] Track has all segments, triggering full cache:", trackId.slice(0, 16) + "...");
      M.downloadAndCacheTrack(trackId);
    }
  };
  
  // Download and cache a full track
  M.downloadAndCacheTrack = async function(trackId) {
    if (M.bulkDownloadStarted.get(trackId)) return M.trackBlobs.get(trackId);
    M.bulkDownloadStarted.set(trackId, true);
    
    try {
      const startTime = performance.now();
      const res = await fetch("/api/tracks/" + encodeURIComponent(trackId));
      const data = await res.arrayBuffer();
      const elapsed = (performance.now() - startTime) / 1000;
      
      // Mark all segments as cached
      const trackCache = M.getTrackCache(trackId);
      for (let i = 0; i < M.SEGMENTS; i++) trackCache.add(i);
      
      // Create blob and URL
      const contentType = res.headers.get("Content-Type") || "audio/mpeg";
      const blob = new Blob([data], { type: contentType });
      const blobUrl = URL.createObjectURL(blob);
      M.trackBlobs.set(trackId, blobUrl);
      
      // Persist to storage
      await TrackStorage.set(trackId, blob, contentType);
      
      // Update cache status and re-render lists
      console.log("[Cache] Track cached:", trackId.slice(0, 16) + "...", "| size:", (data.byteLength / 1024 / 1024).toFixed(2) + "MB");
      M.cachedTracks.add(trackId);
      M.renderQueue();
      M.renderLibrary();
      
      // Update download speed
      if (elapsed > 0 && data.byteLength > 0) {
        M.recentDownloads.push(data.byteLength / elapsed);
        if (M.recentDownloads.length > 5) M.recentDownloads.shift();
        M.downloadSpeed = M.recentDownloads.reduce((a, b) => a + b, 0) / M.recentDownloads.length;
      }
      
      return blobUrl;
    } catch (e) {
      M.bulkDownloadStarted.set(trackId, false);
      return null;
    }
  };
  
  // Fetch a single segment with range request
  async function fetchSegment(i, segStart, segEnd) {
    const trackId = M.currentTrackId;
    const trackCache = M.getTrackCache(trackId);
    if (M.loadingSegments.has(i) || trackCache.has(i)) return;
    M.loadingSegments.add(i);
    try {
      const byteStart = Math.floor(segStart * M.audioBytesPerSecond);
      const byteEnd = Math.floor(segEnd * M.audioBytesPerSecond);
      const startTime = performance.now();
      const res = await fetch("/api/tracks/" + encodeURIComponent(trackId), {
        headers: { "Range": `bytes=${byteStart}-${byteEnd}` }
      });
      const data = await res.arrayBuffer();
      const elapsed = (performance.now() - startTime) / 1000;
      
      // Mark segment as cached
      trackCache.add(i);
      
      // Check if all segments are now cached - if so, trigger full cache
      if (trackCache.size >= M.SEGMENTS && !M.cachedTracks.has(trackId)) {
        console.log("[Cache] All segments complete for:", trackId.slice(0, 16) + "...", "- triggering full cache");
        // Download full track to persist to storage
        M.downloadAndCacheTrack(trackId);
      }
      
      // Update audio bitrate estimate
      const bytesReceived = data.byteLength;
      const durationCovered = segEnd - segStart;
      if (bytesReceived > 0 && durationCovered > 0) {
        M.audioBytesPerSecond = Math.round(bytesReceived / durationCovered);
      }
      
      // Update download speed (rolling average of last 5 downloads)
      if (elapsed > 0 && bytesReceived > 0) {
        M.recentDownloads.push(bytesReceived / elapsed);
        if (M.recentDownloads.length > 5) M.recentDownloads.shift();
        M.downloadSpeed = M.recentDownloads.reduce((a, b) => a + b, 0) / M.recentDownloads.length;
      }
    } catch (e) {}
    M.loadingSegments.delete(i);
  }
  
  // Background bulk download - runs independently
  async function startBulkDownload() {
    const trackId = M.currentTrackId;
    if (!trackId || M.bulkDownloadStarted.get(trackId)) return;
    
    const blobUrl = await M.downloadAndCacheTrack(trackId);
    
    // Switch to blob URL if still on this track
    if (blobUrl && M.currentTrackId === trackId && M.audio.src && !M.audio.src.startsWith("blob:")) {
      const currentTime = M.audio.currentTime;
      const wasPlaying = !M.audio.paused;
      M.audio.src = blobUrl;
      M.audio.currentTime = currentTime;
      if (wasPlaying) M.audio.play().catch(() => {});
    }
  }
  
  // Prefetch missing segments
  let prefetching = false;
  
  M.prefetchSegments = async function() {
    if (prefetching || !M.currentTrackId || !M.audio.src || M.serverTrackDuration <= 0) return;
    prefetching = true;
    
    const segmentDur = M.serverTrackDuration / M.SEGMENTS;
    const missingSegments = [];
    const trackCache = M.getTrackCache(M.currentTrackId);
    
    // Find all missing segments (not in audio buffer AND not in our cache)
    for (let i = 0; i < M.SEGMENTS; i++) {
      if (trackCache.has(i) || M.loadingSegments.has(i)) continue;
      
      const segStart = i * segmentDur;
      const segEnd = (i + 1) * segmentDur;
      let available = false;
      for (let j = 0; j < M.audio.buffered.length; j++) {
        if (M.audio.buffered.start(j) <= segStart && M.audio.buffered.end(j) >= segEnd) {
          available = true;
          break;
        }
      }
      if (!available) {
        missingSegments.push({ i, segStart, segEnd });
      }
    }
    
    if (missingSegments.length > 0) {
      // Fast connection: also start bulk download in background
      if (M.downloadSpeed >= M.FAST_THRESHOLD && !M.bulkDownloadStarted.get(M.currentTrackId)) {
        startBulkDownload(); // Fire and forget
      }
      // Always fetch segments one at a time for seek support
      const s = missingSegments[0];
      await fetchSegment(s.i, s.segStart, s.segEnd);
    }
    
    prefetching = false;
  };
})();
