import assert from 'node:assert/strict';
import test from 'node:test';

import { computePathPlaybackSample, resumeFromPose } from './tokenPathPlayback';
import type { TokenPath } from './tokenPath';
import type { TokenPathPlaybackEntry } from './tokenPathSession';

function path(): TokenPath {
  return {
    points: [
      { nx: 0, ny: 0.5 },
      { nx: 1, ny: 0.5 },
    ],
    closed: false,
    loopMode: 'once',
    durationSec: 2,
    startMode: 'onEnter',
    delaySec: 0,
    facingMode: 'tangentSmooth',
    fixedRotationDeg: 0,
  };
}

function entry(partial: Partial<TokenPathPlaybackEntry>): TokenPathPlaybackEntry {
  return {
    kind: 'token',
    placementId: 't1',
    phase: 'moving',
    segmentStartedAtMs: 0,
    baseDist: 0,
    direction: 1,
    rejoinDist: null,
    durationSec: 2,
    pathLength: 1,
    ...partial,
  };
}

void test('once reaches end and marks done', () => {
  const p = path();
  const r = computePathPlaybackSample({
    path: p,
    entry: entry({ phase: 'moving', segmentStartedAtMs: 0 }),
    nowMs: 2500,
  });
  assert.ok(r);
  assert.equal(r!.markDone, true);
  assert.equal(r!.phase, 'done');
  assert.ok(Math.abs(r!.sample.nx - 1) < 1e-6);
});

void test('delay holds at start until delay elapses', () => {
  const p = { ...path(), startMode: 'delayed' as const, delaySec: 1 };
  const early = computePathPlaybackSample({
    path: p,
    entry: entry({ phase: 'delay', segmentStartedAtMs: 0 }),
    nowMs: 400,
  });
  assert.ok(early);
  assert.equal(early!.phase, 'delay');
  assert.ok(Math.abs(early!.sample.nx) < 1e-6);

  const late = computePathPlaybackSample({
    path: p,
    entry: entry({ phase: 'delay', segmentStartedAtMs: 0 }),
    nowMs: 1500,
  });
  assert.ok(late);
  assert.equal(late!.phase, 'moving');
  assert.ok(late!.sample.nx > 0.2);
});

void test('stopped freezes at baseDist', () => {
  const p = path();
  const r = computePathPlaybackSample({
    path: p,
    entry: entry({ phase: 'stopped', baseDist: 0.25 }),
    nowMs: 99999,
  });
  assert.ok(r);
  assert.equal(r!.phase, 'stopped');
  assert.ok(Math.abs(r!.sample.nx - 0.25) < 1e-6);
});

void test('pingpong reverses after end', () => {
  const p = { ...path(), loopMode: 'pingpong' as const };
  const r = computePathPlaybackSample({
    path: p,
    entry: entry({ phase: 'moving', durationSec: 2, pathLength: 1 }),
    nowMs: 3000, // 1.5 path lengths → back toward start
  });
  assert.ok(r);
  assert.equal(r!.markDone, false);
  assert.ok(r!.sample.nx < 0.6);
});

void test('resumeFromPose picks ahead distance', () => {
  const p = path();
  const d = resumeFromPose(p, 0.4, 0.5, 0.1);
  assert.ok(d >= 0.1);
  assert.ok(d <= 1);
});
