import { sampleUrl, type SoundPhase, type SwitchManifest } from './keySoundMap';
import type { SwitchType } from '../types';

export type Manifest = Record<string, SwitchManifest>;

/** Cache key format: `{switchType}/{phase}/{sampleKey}` */
function bufferKey(switchType: SwitchType, phase: SoundPhase, sampleKey: string): string {
  return `${switchType}/${phase}/${sampleKey}`;
}

export type SampleCache = {
  ensureManifest: () => Promise<Manifest>;
  loadBuffer: (
    switchType: SwitchType,
    phase: SoundPhase,
    sampleKey: string,
  ) => Promise<AudioBuffer>;
  preloadSwitch: (switchType: SwitchType) => Promise<void>;
  getLoadedSwitch: () => SwitchType | null;
};

/**
 * Shared MP3 fetch + decode cache used by live preview and offline export.
 * Requires a BaseAudioContext for `decodeAudioData` (live AudioContext or throwaway decode context).
 */
export function createSampleCache(decodeContext: BaseAudioContext): SampleCache {
  let manifest: Manifest | null = null;
  let loadedSwitch: SwitchType | null = null;
  const buffers = new Map<string, AudioBuffer>();

  async function ensureManifest(): Promise<Manifest> {
    if (manifest) return manifest;
    const response = await fetch('/assets/samples/kbsim/manifest.json');
    if (!response.ok) throw new Error('Failed to load sample manifest');
    manifest = (await response.json()) as Manifest;
    return manifest;
  }

  async function loadBuffer(
    switchType: SwitchType,
    phase: SoundPhase,
    sampleKey: string,
  ): Promise<AudioBuffer> {
    const key = bufferKey(switchType, phase, sampleKey);
    const cached = buffers.get(key);
    if (cached) return cached;

    const response = await fetch(sampleUrl(switchType, phase, sampleKey));
    if (!response.ok) {
      throw new Error(`Sample not found: ${sampleUrl(switchType, phase, sampleKey)}`);
    }
    const arrayBuffer = await response.arrayBuffer();
    const audioBuffer = await decodeContext.decodeAudioData(arrayBuffer);
    buffers.set(key, audioBuffer);
    return audioBuffer;
  }

  /** Preload all press samples and release samples (skips GENERIC_long) for a switch. */
  async function preloadSwitch(switchType: SwitchType): Promise<void> {
    const data = await ensureManifest();
    const entry = data[switchType];
    if (!entry) throw new Error(`Unknown switch: ${switchType}`);

    const loads: Promise<AudioBuffer>[] = [];
    for (const sampleKey of entry.press) {
      loads.push(loadBuffer(switchType, 'press', sampleKey));
    }
    for (const sampleKey of entry.release) {
      if (sampleKey === 'GENERIC_long') continue;
      loads.push(loadBuffer(switchType, 'release', sampleKey));
    }
    await Promise.all(loads);
    loadedSwitch = switchType;
  }

  return {
    ensureManifest,
    loadBuffer,
    preloadSwitch,
    getLoadedSwitch: () => loadedSwitch,
  };
}
