import assert from "node:assert/strict";
import { after, before, test } from "node:test";

import { createServer } from "vite";

let server;
let attemptRecordedAutoplay;
let createLatestAnimationFrameEmitter;

before(async () => {
  server = await createServer({
    appType: "custom",
    logLevel: "silent",
    server: { middlewareMode: true },
  });
  ({ attemptRecordedAutoplay, createLatestAnimationFrameEmitter } =
    await server.ssrLoadModule("/src/components/RerunViewport.tsx"));
});

after(async () => {
  await server?.close();
});

test("recorded autoplay reports success only after seek and play both succeed", () => {
  let seekAttempts = 0;
  let playAttempts = 0;
  const seekToStart = () => {
    seekAttempts += 1;
  };
  const startPlaying = () => {
    playAttempts += 1;
    if (playAttempts === 1) throw new Error("receiver is not ready yet");
  };

  assert.equal(attemptRecordedAutoplay(seekToStart, startPlaying), false);
  assert.equal(attemptRecordedAutoplay(seekToStart, startPlaying), true);
  assert.equal(seekAttempts, 2);
  assert.equal(playAttempts, 2);
});

test("recorded autoplay does not try play when the seek itself fails", () => {
  let playAttempts = 0;

  assert.equal(attemptRecordedAutoplay(
    () => {
      throw new Error("timeline is not ready yet");
    },
    () => {
      playAttempts += 1;
    },
  ), false);
  assert.equal(playAttempts, 0);
});

test("time updates coalesce to the latest value once per animation frame", () => {
  let nextHandle = 1;
  const frames = new Map();
  const cancelled = [];
  const emitted = [];
  const emitter = createLatestAnimationFrameEmitter({
    emit(value) {
      emitted.push(value);
    },
    requestFrame(callback) {
      const handle = nextHandle;
      nextHandle += 1;
      frames.set(handle, callback);
      return handle;
    },
    cancelFrame(handle) {
      cancelled.push(handle);
      frames.delete(handle);
    },
  });

  emitter.push(1);
  emitter.push(2);
  emitter.push(3);
  assert.equal(frames.size, 1);
  assert.deepEqual(emitted, []);

  const firstFrame = frames.get(1);
  frames.delete(1);
  firstFrame(0);
  assert.deepEqual(emitted, [3]);

  emitter.push(4);
  assert.equal(frames.size, 1);
  emitter.cancel();
  assert.deepEqual(cancelled, [2]);
  assert.deepEqual(emitted, [3]);

  emitter.push(5);
  assert.equal(frames.size, 0);
  assert.deepEqual(emitted, [3]);
});
