import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js';

import { ApiClient } from '@/lib/apiClient';
import { Clip } from '@/state/clipStore';
import { getOrGenerateWavFileUrl, logAndGetMediaBlobs } from '@/utils/download';

import downloadBlob from './downloadBlob';

export interface MidiDataForClip {
  clipId: string;
  midiBlob: Blob;
  fileName: string;
}

export default async function downloadAsZip(
  apiClient: ApiClient,
  clips: Clip[],
  zipFilenameWithoutExtension: string,
  format: 'mp3' | 'wav' = 'mp3',
  midiData: MidiDataForClip[] = []
) {
  const zip = new ZipWriter(new BlobWriter('application/zip'));

  const nameCounts: Record<string, number> = {};

  const blobs =
    // todo: cleanup this abstraction. wavs won't work unless you pass in a download URL :|
    (
      await logAndGetMediaBlobs(
        apiClient,
        await Promise.all(
          clips.map(async (clip) => ({
            clip,
            downloadUrl:
              format === 'wav'
                ? await getOrGenerateWavFileUrl(apiClient, clip.id)
                : undefined,
          }))
        ),
        format === 'wav' ? 'audio-wav' : 'audio'
      )
    )
      .map((blob, i) => ({
        blob,
        clip: clips[i],
      }))
      .sort((a, b) => a.clip.id.localeCompare(b.clip.id));

  for (const { blob: audioFileBlob, clip } of blobs) {
    if (!audioFileBlob) continue;

    const audioFileUrl = clip.audio_url;
    const fileNameWithoutExtension = clip.title || 'Untitled';
    if (!audioFileUrl) continue;

    const mimeType = audioFileBlob.type;

    // Map MIME type to file extension
    const mimeToExtension: Record<string, string> = {
      'audio/mpeg': 'mp3',
      'audio/mp4': 'm4a',
      'audio/wav': 'wav',
      'audio/x-wav': 'wav',
      'audio/ogg': 'ogg',
      'audio/webm': 'webm',
      'audio/aac': 'aac',
    };

    const extension = mimeToExtension[mimeType] || 'mp3'; // fallback to mp3 if unknown mime type

    let numberedFileName = fileNameWithoutExtension;
    while (nameCounts[numberedFileName]) {
      nameCounts[numberedFileName]++;
      numberedFileName = `${fileNameWithoutExtension}_${nameCounts[numberedFileName] - 1}`;
    }
    nameCounts[numberedFileName] = 1;
    await zip.add(
      `${numberedFileName}.${extension}`,
      new BlobReader(audioFileBlob)
    );
  }

  Object.keys(nameCounts).forEach((key) => delete nameCounts[key]);

  // Add MIDI files to the zip
  for (const { midiBlob, fileName } of midiData.sort((a, b) =>
    a.clipId.localeCompare(b.clipId)
  )) {
    if (!midiBlob) continue;

    // Handle filename conflicts for MIDI files
    let numberedFileName = fileName;
    while (nameCounts[numberedFileName]) {
      nameCounts[numberedFileName]++;
      numberedFileName = `${fileName}_${nameCounts[numberedFileName] - 1}`;
    }
    nameCounts[numberedFileName] = 1;

    await zip.add(`${numberedFileName}.mid`, new BlobReader(midiBlob));
  }

  const zipBlob = await zip.close();
  downloadBlob(zipBlob, `${zipFilenameWithoutExtension}.zip`);
}
