/**
 * Global audio loading queue service to prevent hitting browser connection limits
 * Will yield to image loading if images are queued
 */

import { hasQueuedImages } from "./ImageLoader";

// Configuration
const MAX_CONCURRENT_LOADS = 1;
const RETRY_DELAY_MS = 200; // Time to wait before retrying if yielding to images

// State
let activeLoads = 0;
const audioQueue: Array<{
  src: string;
  onComplete: (blobUrl: string) => void;
  onError: (error: Error) => void;
}> = [];

// Cache of loaded audio files (src -> blobUrl)
const loadedAudioCache: Record<string, string> = {};

// Keep track of pending loads to avoid duplicates
const pendingLoads: Set<string> = new Set();

/**
 * Queue an audio file for loading, respecting the global concurrent loading limit
 * Returns a promise that resolves with the blob URL when loading is complete
 */
export function queueAudioForLoading(
  src: string,
  onComplete?: (blobUrl: string) => void,
  onError?: (error: Error) => void
): Promise<string> {
  // Return cached result immediately if available
  if (loadedAudioCache[src]) {
    console.debug(`[AudioLoader] Using cached audio for ${src}`);
    const blobUrl = loadedAudioCache[src];
    if (onComplete) setTimeout(() => onComplete(blobUrl), 0);
    return Promise.resolve(blobUrl);
  }

  // If this source is already being loaded, add to existing promises
  if (pendingLoads.has(src)) {
    console.debug(
      `[AudioLoader] Already loading ${src}, adding to pending requests`
    );
    return new Promise((resolve, reject) => {
      audioQueue.push({
        src,
        onComplete: (blobUrl) => {
          if (onComplete) onComplete(blobUrl);
          resolve(blobUrl);
        },
        onError: (error) => {
          if (onError) onError(error);
          reject(error);
        },
      });
    });
  }

  // Mark as pending
  pendingLoads.add(src);

  return new Promise((resolve, reject) => {
    // Add to queue
    audioQueue.push({
      src,
      onComplete: (blobUrl) => {
        if (onComplete) onComplete(blobUrl);
        resolve(blobUrl);
      },
      onError: (error) => {
        if (onError) onError(error);
        reject(error);
      },
    });

    // Try to process queue
    processQueue();
  });
}

/**
 * Process the next items in the queue if we have capacity
 */
function processQueue() {
  // Check if there are any images being loaded or queued - yield to them if so
  if (hasQueuedImages()) {
    console.debug("[AudioLoader] Yielding to image loader, will retry later");
    // Schedule a retry after a short delay
    setTimeout(processQueue, RETRY_DELAY_MS);
    return;
  }

  // Group queue items by src to avoid duplicate loads
  const nextBatch: Record<
    string,
    Array<{
      onComplete: (blobUrl: string) => void;
      onError: (error: Error) => void;
    }>
  > = {};

  // Find next items to process while respecting MAX_CONCURRENT_LOADS
  while (activeLoads < MAX_CONCURRENT_LOADS && audioQueue.length > 0) {
    const next = audioQueue.shift();
    if (!next) continue;

    // If already cached, complete immediately without consuming a slot
    if (loadedAudioCache[next.src]) {
      next.onComplete(loadedAudioCache[next.src]);
      continue;
    }

    // Group by src
    if (!nextBatch[next.src]) {
      nextBatch[next.src] = [];
      // Each unique src counts as one active load
      activeLoads++;
    }

    nextBatch[next.src].push({
      onComplete: next.onComplete,
      onError: next.onError,
    });
  }

  // Start loading each unique audio file
  Object.entries(nextBatch).forEach(([src, handlers]) => {
    loadAudio(src, handlers);
  });

  // If we have more items in the queue but didn't process them due to capacity limits,
  // and no images are queued, schedule another processQueue call to check again soon
  if (audioQueue.length > 0 && !hasQueuedImages()) {
    setTimeout(processQueue, RETRY_DELAY_MS);
  }
}

/**
 * Internal function to handle the actual audio loading using fetch API
 * This ensures the entire audio file is downloaded
 * @param src The source URL to load
 * @param handlers Array of handlers to call when loading completes or fails
 */
async function loadAudio(
  src: string,
  handlers: Array<{
    onComplete: (blobUrl: string) => void;
    onError: (error: Error) => void;
  }>
) {
  console.debug(`[AudioLoader] Loading ${src} (${handlers.length} listeners)`);

  try {
    // Fetch the entire audio file
    const response = await fetch(src);

    if (!response.ok) {
      throw new Error(`Failed to load audio: ${response.statusText}`);
    }

    // Convert to blob to ensure full download
    const blob = await response.blob();

    // Create a blob URL to use as the audio source
    const blobUrl = URL.createObjectURL(blob);

    // Store in cache
    loadedAudioCache[src] = blobUrl;

    console.debug(`[AudioLoader] Successfully loaded ${src}`);

    // Call all completion handlers
    handlers.forEach((handler) => handler.onComplete(blobUrl));
  } catch (error) {
    console.error(`[AudioLoader] Error loading audio: ${error}`);

    // Call all error handlers
    handlers.forEach((handler) => handler.onError(error as Error));
  } finally {
    // Remove from pending loads
    pendingLoads.delete(src);

    // Decrement counter when audio is loaded (or failed)
    activeLoads--;

    // Process next audio in queue if available
    processQueue();
  }
}
