export function readAudioDuration(path, { createAudio = source => new Audio(source) } = {}) {
  return new Promise((resolve, reject) => {
    const audio = createAudio(path.startsWith('/') ? path : `/${path}`);
    audio.onloadedmetadata = () => resolve(audio.duration);
    audio.onerror = reject;
  });
}

export function waitForAudioReady(audio, {
  minimumReadyState = 2,
  intervalMs = 100,
  timeoutMs = 10000,
  setTimer = setTimeout,
  now = () => Date.now(),
} = {}) {
  const startedAt = now();
  return new Promise((resolve, reject) => {
    const check = () => {
      if (audio.readyState >= minimumReadyState) {
        resolve();
      } else if (now() - startedAt >= timeoutMs) {
        reject(new Error('Timed out waiting for audio playback data'));
      } else {
        setTimer(check, intervalMs);
      }
    };
    check();
  });
}

export async function loadProjectAudio(path, {
  getCurrentDuration,
  saveUndo,
  showProgress,
  updateProgress = showProgress,
  hideProgress,
  readDuration = value => readAudioDuration(value),
  setAudioState,
  loadPlayback,
  waitUntilReady,
  scaleTimeline,
  clearWaveform,
  analyze,
  setBpm,
  refresh,
  save,
  delay = ms => new Promise(resolve => setTimeout(resolve, ms)),
}) {
  saveUndo();
  showProgress('Loading audio file...', 10);
  try {
    const newDuration = await readDuration(path);
    const oldDuration = getCurrentDuration();
    updateProgress('Reading audio metadata...', 30);
    setAudioState(path, newDuration);
    updateProgress('Loading audio for playback...', 45);
    await loadPlayback();
    await waitUntilReady();
    updateProgress('Scaling timeline...', 50);
    if (oldDuration > 0 && newDuration !== oldDuration) {
      scaleTimeline(oldDuration, newDuration);
    }
    updateProgress('Analysing audio & detecting BPM...', 60);
    clearWaveform();
    const analysis = await analyze(path);
    if (analysis.bpm) setBpm(analysis.bpm);
    updateProgress('Updating timeline...', 80, analysis.bpm || null);
    refresh();
    updateProgress('Saving project...', 90);
    await save();
    updateProgress('Done!', 100);
    await delay(500);
    hideProgress();
    return { duration: newDuration, analysis };
  } catch (error) {
    hideProgress();
    throw error;
  }
}
