// The only clock the engine has. Scene and layer code never reads wall time —
// it reads whatever this hands it. That is what lets a realtime preview and an
// offline export produce identical frames.
//
// dt is CONSTANT (1/fps) in both modes. A dropped frame in preview shows as a
// hitch on screen; it never changes what gets rendered.

export const REALTIME = 'realtime';
export const FIXED = 'fixed';

export class Timeline {
    constructor({ fps = 60, duration = 0, mode = REALTIME } = {}) {
        this.fps = fps;
        this.duration = duration;
        this.mode = mode;
        this.frame = 0;
        this.playing = false;
    }

    get dt() {
        return 1 / this.fps;
    }

    get time() {
        return this.frame / this.fps;
    }

    get frameCount() {
        return Math.max(1, Math.round(this.duration * this.fps));
    }

    get progress() {
        return this.duration > 0 ? Math.min(1, this.time / this.duration) : 0;
    }

    get finished() {
        return this.frame >= this.frameCount - 1;
    }

    setDuration(seconds) {
        this.duration = seconds;
        return this;
    }

    /** Fixed-step advance. Used by the exporter and by preview warm-up. */
    advance(frames = 1) {
        this.frame = Math.min(this.frameCount - 1, this.frame + frames);
        return this.frame;
    }

    seek(frame) {
        this.frame = Math.max(0, Math.min(this.frameCount - 1, Math.round(frame)));
        return this.frame;
    }

    seekTime(seconds) {
        return this.seek(seconds * this.fps);
    }

    /**
     * Realtime mode: derive the frame index from the audio element's clock.
     * Audio is the master — the visuals follow it, never the other way round,
     * so a slow GPU desynchronises nothing.
     */
    syncToAudio(currentTime) {
        this.frame = Math.max(0, Math.min(this.frameCount - 1, Math.floor(currentTime * this.fps)));
        return this.frame;
    }

    /** A snapshot handed to layers, so nothing holds a mutable reference to the clock. */
    snapshot() {
        return {
            frame: this.frame,
            time: this.time,
            dt: this.dt,
            fps: this.fps,
            progress: this.progress,
            duration: this.duration,
        };
    }
}
