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

import * as Comlink from 'comlink';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';
import type { AudioFormat } from '../core/types';
import { AUDIO_CODEC_CONFIG, FORMAT_TO_MIME, getFileExtension } from '../core/constants';

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!;
}

// -----------------------------------------------------------------------------
// Command Executor (DRY: Handles Write → Run → Read → Clean lifecycle)
// -----------------------------------------------------------------------------

/**
 * Runs an FFmpeg command with guaranteed cleanup.
 * This abstracts the entire file I/O lifecycle.
 */
async function runFFmpegCommand(
  inputBuffer: ArrayBuffer,
  inputExt: string,
  outputExt: string,
  args: string[],
  onProgress?: (progress: number) => void
): Promise<Uint8Array> {
  const ff = await ensureFFmpegLoaded();
  const inputFile = `input.${inputExt}`;
  const outputFile = `output.${outputExt}`;

  // Store callback reference for proper cleanup
  const progressCallback = onProgress 
    ? ({ progress }: { progress: number }) => onProgress(Math.round(progress * 100))
    : null;

  if (progressCallback) {
    ff.on('progress', progressCallback);
  }

  try {
    // Write input file to FFmpeg's virtual filesystem
    await ff.writeFile(inputFile, new Uint8Array(inputBuffer));
    
    // Execute FFmpeg command
    await ff.exec(['-i', inputFile, ...args, '-y', outputFile]);

    // Read output file
    const data = await ff.readFile(outputFile);
    return data as Uint8Array;
  } finally {
    // Guaranteed cleanup (even if exec crashes)
    try {
      await ff.deleteFile(inputFile);
      await ff.deleteFile(outputFile);
      if (progressCallback) {
        ff.off('progress', progressCallback);
      }
    } catch {
      // Ignore cleanup errors
    }
  }
}

// -----------------------------------------------------------------------------
// Audio Converter API
// -----------------------------------------------------------------------------

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 = 192,
    onProgress?: (progress: number) => void
  ): Promise<Blob> {
    const getArgs = AUDIO_CODEC_CONFIG[outputFormat];
    if (!getArgs) {
      throw new Error(`Unsupported output format: ${outputFormat}`);
    }

    const inputExt = getFileExtension(inputFormat);
    const outputExt = getFileExtension(outputFormat);

    const resultData = await runFFmpegCommand(
      arrayBuffer,
      inputExt,
      outputExt,
      getArgs(bitrate),
      onProgress
    );

    // Copy to new buffer to avoid SharedArrayBuffer issues
    const buffer = new ArrayBuffer(resultData.length);
    new Uint8Array(buffer).set(resultData);
    return new Blob([buffer], { type: FORMAT_TO_MIME[outputFormat] });
  },

  /**
   * Extract audio from video file (useful for iPhone MOV files)
   */
  async extractAudio(
    arrayBuffer: ArrayBuffer,
    inputExtension: string,
    outputFormat: AudioFormat,
    bitrate: number = 192,
    onProgress?: (progress: number) => void
  ): Promise<Blob> {
    const getArgs = AUDIO_CODEC_CONFIG[outputFormat];
    if (!getArgs) {
      throw new Error(`Unsupported output format: ${outputFormat}`);
    }

    // Add -vn (no video) to the codec args
    const args = ['-vn', ...getArgs(bitrate)];
    const outputExt = getFileExtension(outputFormat);

    const resultData = await runFFmpegCommand(
      arrayBuffer,
      inputExtension,
      outputExt,
      args,
      onProgress
    );

    // Copy to new buffer to avoid SharedArrayBuffer issues
    const buffer = new ArrayBuffer(resultData.length);
    new Uint8Array(buffer).set(resultData);
    return new Blob([buffer], { type: FORMAT_TO_MIME[outputFormat] });
  },
};

Comlink.expose(audioConverter);

export type AudioConverterWorker = typeof audioConverter;
