// double buffered anim loop: draws offscreen then gets placed to
//  visible canvas in one op
export function createPreviewLoop(
  canvas: HTMLCanvasElement,
  fps: number,
  render: (offscreen: HTMLCanvasElement, dt: number) => void,
): () => void {
  const fixedDt = 1 / fps;
  let nextFrame = performance.now() + fixedDt * 1000;

  const offscreen = document.createElement("canvas");
  offscreen.width = canvas.width;
  offscreen.height = canvas.height;
  const displayCtx = canvas.getContext("2d")!;

  let raf: number;

  // using this prevents ghost-loops; if cleanup is called mid-draw,
  //  loop won't re-schedule via rAF at end of tick and prevents
  //  old loop running forever and fighting new preview for canvas
  let stopped = false;

  function loop(now: number) {
    if (stopped) return;
    if (now >= nextFrame) {
      // fps limiting: skips rAF callbacks until the next frame deadline
      //  then catches up if the callback was delayed
      nextFrame += fixedDt * 1000;
      if (nextFrame < now) nextFrame = now + fixedDt * 1000;
      render(offscreen, fixedDt);
      displayCtx.drawImage(offscreen, 0, 0);
    }
    if (!stopped) raf = requestAnimationFrame(loop);
  }

  raf = requestAnimationFrame(loop);

  // cleanup function: called once to stop loop and cancel pending animation
  return () => {
    stopped = true;
    cancelAnimationFrame(raf);
  };
}

// generates Uint8Array with an animated sine wave
// useful for previews that need real audio data placeholder
export function generateSyntheticFreq(
  freq: Uint8Array,
  phase: number,
) {
  const size = freq.length;
  for (let i = 0; i < size; i++) {
    const t = (i / size) * Math.PI * 2 + phase;
    freq[i] = Math.floor((Math.sin(t) * 0.5 + 0.5) * 255);
  }
}
