// =============================================================================
// Audio Worker
// Handles audio conversions using FFmpeg WASM
// =============================================================================

import * as Comlink from 'comlink';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';

// All supported audio formats
type AudioFormat = 
  | 'mp3' | 'wav' | 'ogg' | 'm4a' | 'flac'
  | 'aac' | 'aiff' | 'alac' | 'wma' | 'opus' | 'm4r' | 'amr';

let ffmpeg: FFmpeg | null = null;
let ffmpegLoading: Promise<void> | null = null;

async function ensureFFmpegLoaded(): Promise<FFmpeg> {
  if (ffmpeg?.loaded) return ffmpeg;

  if (ffmpegLoading) {
    await ffmpegLoading;
    return ffmpeg!;
  }

  ffmpegLoading = (async () => {
    ffmpeg = new FFmpeg();

    // Load FFmpeg core from CDN with proper CORS
    const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
    
    await ffmpeg.load({
      coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
      wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
    });
    
    console.log('[AudioWorker] FFmpeg WASM initialized');
  })();

  await ffmpegLoading;
  return ffmpeg!;
}

function getFFmpegArgs(
  _inputFormat: AudioFormat,
  outputFormat: AudioFormat,
  bitrate?: number
): string[] {
  const args: string[] = [];
  const br = bitrate || 192;

  // Codec selection based on output format
  switch (outputFormat) {
    case 'mp3':
      args.push('-c:a', 'libmp3lame');
      args.push('-b:a', `${br}k`);
      break;
    case 'wav':
      args.push('-c:a', 'pcm_s16le');
      break;
    case 'ogg':
      args.push('-c:a', 'libvorbis');
      args.push('-q:a', '4');
      break;
    case 'm4a':
      args.push('-c:a', 'aac');
      args.push('-b:a', `${br}k`);
      break;
    case 'flac':
      args.push('-c:a', 'flac');
      args.push('-compression_level', '5');
      break;
    case 'aac':
      args.push('-c:a', 'aac');
      args.push('-b:a', `${br}k`);
      break;
    case 'aiff':
      args.push('-c:a', 'pcm_s16be');
      break;
    case 'alac':
      // ALAC in M4A container
      args.push('-c:a', 'alac');
      break;
    case 'wma':
      args.push('-c:a', 'wmav2');
      args.push('-b:a', `${br}k`);
      break;
    case 'opus':
      args.push('-c:a', 'libopus');
      args.push('-b:a', `${br}k`);
      break;
    case 'm4r':
      // M4R is just AAC with different extension (iPhone ringtones)
      args.push('-c:a', 'aac');
      args.push('-b:a', `${br}k`);
      break;
    case 'amr':
      args.push('-c:a', 'libopencore_amrnb');
      args.push('-ar', '8000'); // AMR requires 8kHz sample rate
      args.push('-ac', '1');    // AMR is mono only
      break;
  }

  return args;
}

function getExtension(format: AudioFormat): string {
  // ALAC uses m4a container
  if (format === 'alac') return 'm4a';
  return format;
}

function getMimeType(format: AudioFormat): string {
  const mimeMap: Record<AudioFormat, string> = {
    mp3: 'audio/mpeg',
    wav: 'audio/wav',
    ogg: 'audio/ogg',
    m4a: 'audio/mp4',
    flac: 'audio/flac',
    aac: 'audio/aac',
    aiff: 'audio/aiff',
    alac: 'audio/x-m4a',
    wma: 'audio/x-ms-wma',
    opus: 'audio/opus',
    m4r: 'audio/x-m4r',
    amr: 'audio/amr',
  };
  return mimeMap[format];
}

const audioConverter = {
  /**
   * Initialize the worker (can be called early to warm up)
   */
  async init(): Promise<boolean> {
    try {
      await ensureFFmpegLoaded();
      return true;
    } catch (error) {
      console.error('[AudioWorker] Init failed:', error);
      return false;
    }
  },

  /**
   * Convert audio file using FFmpeg
   */
  async convert(
    arrayBuffer: ArrayBuffer,
    inputFormat: AudioFormat,
    outputFormat: AudioFormat,
    bitrate?: number,
    onProgress?: (progress: number) => void
  ): Promise<Blob> {
    const ff = await ensureFFmpegLoaded();

    const inputExt = getExtension(inputFormat);
    const outputExt = getExtension(outputFormat);
    const inputFileName = `input.${inputExt}`;
    const outputFileName = `output.${outputExt}`;

    // Register progress callback if provided
    if (onProgress) {
      ff.on('progress', ({ progress }) => {
        onProgress(Math.round(progress * 100));
      });
    }

    try {
      // Write input file to FFmpeg's virtual filesystem
      await ff.writeFile(inputFileName, new Uint8Array(arrayBuffer));

      // Build FFmpeg command
      const codecArgs = getFFmpegArgs(inputFormat, outputFormat, bitrate);
      
      await ff.exec([
        '-i', inputFileName,
        ...codecArgs,
        '-y', // Overwrite output
        outputFileName,
      ]);

      // Read output file
      const data = await ff.readFile(outputFileName);
      
      // Cleanup
      await ff.deleteFile(inputFileName);
      await ff.deleteFile(outputFileName);

      // Convert to regular ArrayBuffer to avoid SharedArrayBuffer issues
      const buffer = new ArrayBuffer((data as Uint8Array).length);
      new Uint8Array(buffer).set(data as Uint8Array);
      return new Blob([buffer], { type: getMimeType(outputFormat) });
    } catch (error) {
      // Attempt cleanup on error
      try {
        await ff.deleteFile(inputFileName);
        await ff.deleteFile(outputFileName);
      } catch {
        // Ignore cleanup errors
      }
      throw error;
    }
  },

  /**
   * Extract audio from video file (useful for iPhone MOV files)
   */
  async extractAudio(
    arrayBuffer: ArrayBuffer,
    inputExtension: string,
    outputFormat: AudioFormat,
    bitrate?: number,
    onProgress?: (progress: number) => void
  ): Promise<Blob> {
    const ff = await ensureFFmpegLoaded();

    const inputFileName = `input.${inputExtension}`;
    const outputExt = getExtension(outputFormat);
    const outputFileName = `output.${outputExt}`;

    if (onProgress) {
      ff.on('progress', ({ progress }) => {
        onProgress(Math.round(progress * 100));
      });
    }

    try {
      await ff.writeFile(inputFileName, new Uint8Array(arrayBuffer));

      const codecArgs = getFFmpegArgs('m4a', outputFormat, bitrate);
      
      await ff.exec([
        '-i', inputFileName,
        '-vn', // No video
        ...codecArgs,
        '-y',
        outputFileName,
      ]);

      const data = await ff.readFile(outputFileName);
      
      await ff.deleteFile(inputFileName);
      await ff.deleteFile(outputFileName);

      // Convert to regular ArrayBuffer
      const buffer = new ArrayBuffer((data as Uint8Array).length);
      new Uint8Array(buffer).set(data as Uint8Array);
      return new Blob([buffer], { type: getMimeType(outputFormat) });
    } catch (error) {
      try {
        await ff.deleteFile(inputFileName);
        await ff.deleteFile(outputFileName);
      } catch {
        // Ignore cleanup errors
      }
      throw error;
    }
  },
};

Comlink.expose(audioConverter);

export type AudioConverterWorker = typeof audioConverter;
