/**
 * Pure playback sampling for token paths (shared by control / presentation / editor preview).
 */

import {
  findNearestAheadDistance,
  sampleTokenPathAtDistance,
  tokenPathTotalLength,
  type TokenPath,
  type TokenPathSample,
} from './tokenPath';
import type { TokenPathPlaybackEntry } from './tokenPathSession';

export function computePathPlaybackSample(args: {
  path: TokenPath;
  entry: TokenPathPlaybackEntry;
  nowMs: number;
}): { sample: TokenPathSample; phase: TokenPathPlaybackEntry['phase']; markDone: boolean } | null {
  const { path, entry, nowMs } = args;
  const length = entry.pathLength > 1e-9 ? entry.pathLength : tokenPathTotalLength(path);
  if (length <= 1e-9) return null;
  const durationMs = Math.max(0.5, entry.durationSec) * 1000;
  const speed = length / durationMs;

  if (entry.phase === 'stopped' || entry.phase === 'done') {
    const sample = sampleTokenPathAtDistance(path, clampDist(entry.baseDist, length));
    if (!sample) return null;
    return { sample, phase: entry.phase, markDone: false };
  }

  let elapsed = Math.max(0, nowMs - entry.segmentStartedAtMs);

  if (entry.phase === 'delay') {
    const delayMs = Math.max(0, path.delaySec) * 1000;
    if (elapsed < delayMs) {
      const start = sampleTokenPathAtDistance(path, 0);
      if (!start) return null;
      return { sample: start, phase: 'delay', markDone: false };
    }
    elapsed -= delayMs;
  }

  // Moving (including post-delay).
  let base = entry.baseDist;
  let dir: 1 | -1 = entry.direction || 1;
  let travel = speed * elapsed;

  if (entry.rejoinDist != null && Number.isFinite(entry.rejoinDist)) {
    const target = clampDist(entry.rejoinDist, length);
    const need = Math.abs(target - base);
    if (travel < need) {
      const dist = base + Math.sign(target - base || 1) * travel;
      const sample = sampleTokenPathAtDistance(path, clampDist(dist, length));
      if (!sample) return null;
      return { sample, phase: 'moving', markDone: false };
    }
    travel -= need;
    base = target;
  }

  return advanceAlongPath({
    path,
    length,
    baseDist: base,
    direction: dir,
    travelDist: travel,
  });
}

function clampDist(d: number, length: number): number {
  return Math.max(0, Math.min(length, d));
}

function advanceAlongPath(args: {
  path: TokenPath;
  length: number;
  baseDist: number;
  direction: 1 | -1;
  travelDist: number;
}): { sample: TokenPathSample; phase: TokenPathPlaybackEntry['phase']; markDone: boolean } | null {
  const { path, length, baseDist, direction, travelDist } = args;
  const loopMode = path.loopMode;
  const closed = path.closed;

  let dist = baseDist + direction * travelDist;
  let markDone = false;

  if (loopMode === 'pingpong') {
    const period = Math.max(length * 2, 1e-9);
    let d = ((dist % period) + period) % period;
    if (d > length) d = period - d;
    dist = d;
  } else if (loopMode === 'loop' && closed) {
    dist = ((dist % length) + length) % length;
  } else {
    // once (or loop without closed)
    if (dist >= length) {
      dist = length;
      markDone = true;
    } else if (dist <= 0) {
      dist = 0;
      markDone = true;
    }
  }

  const sample = sampleTokenPathAtDistance(path, dist);
  if (!sample) return null;
  return { sample, phase: markDone ? 'done' : 'moving', markDone };
}

export function resumeFromPose(path: TokenPath, nx: number, ny: number, fromDist: number): number {
  return findNearestAheadDistance(path, nx, ny, fromDist);
}
