<script lang="ts" setup>
const canvas = useTemplateRef("canvas");

const updateCallbacks = new Set<UpdateCallback>();
const renderCallbacks = new Set<RenderCallback>();

let ctx: CanvasRenderingContext2D | null = null;
let animationFrameId: number | null = null;
let lastFrameTime = 0;
let lastRealFrameTime = 0;

const registerUpdateCallback = (callback: UpdateCallback) => {
  updateCallbacks.add(callback);
  return () => updateCallbacks.delete(callback);
};

const registerRenderCallback = (callback: RenderCallback) => {
  renderCallbacks.add(callback);
  return () => renderCallbacks.delete(callback);
};

const renderFrame = (timestamp: number) => {
  if (!ctx) return;

  const deltaTime = timestamp - lastFrameTime;
  lastFrameTime = timestamp;

  const start = Date.now();

  // update
  for (const callback of updateCallbacks) {
    callback(deltaTime, lastRealFrameTime);
  }

  // render
  ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

  for (const callback of renderCallbacks) {
    callback(ctx);
  }

  lastRealFrameTime = Date.now() - start;

  animationFrameId = requestAnimationFrame(renderFrame);
};

onMounted(() => {
  if (!canvas.value) throw new Error("Missing canvas");

  ctx = canvas.value.getContext("2d");
  if (!ctx) throw new Error("Missing 2d context");

  provide("registerUpdateCallback", registerUpdateCallback);
  provide("registerRenderCallback", registerRenderCallback);

  animationFrameId = requestAnimationFrame(renderFrame);
});

onUnmounted(() => {
  if (animationFrameId !== null) {
    cancelAnimationFrame(animationFrameId);
  }
});
</script>

<template>
  <canvas
    ref="canvas"
    :width="SCREEN_WIDTH"
    :height="SCREEN_HEIGHT"
    :style="{
      margin: '0',
      border: '1px solid red',
    }"
  />

  <slot v-if="canvas" />
</template>
