import EventEmitter from 'eventemitter3';

import type { Service } from '../types';

/** Events emitted by the clock service. */
export type ClockEvents = "tick" | "paused" | "resumed";
/**
 * Monotonic ticker backed by {@link requestAnimationFrame}.
 * Keeps track of elapsed time and notifies listeners about frame deltas.
 */
export default class Clock extends EventEmitter<ClockEvents> implements Service
{
    public name = 'Clock';
    public readonly serviceType = 'clock';

    public elapsedTime = 0;
    public deltaMS = 0;

    protected start = 0;
    public paused = false;
    public timeScale = 1;
    protected previousNonZeroTimeScale = 1;

    /** Optional animation speed presets used by the debug panel. */
    public animSpeedConfig?: Record<string, Record<string, number>>;

    /** Applies the selected animation speed preset if provided by the host game. */
    public setAnimSpeed?: () => void;

    private rafId?: number;

    /** Starts the ticking loop immediately. */
    public run(): void
    {
        this.tick(performance.now());
    }

    /**
     * Advances the timer and emits a `tick` event with the frame delta.
     * @param timestamp
     */
    protected tick(timestamp: number): void
    {
        if (this.paused)
        {
            return;
        }

        if (this.start === 0)
        {
            this.start = timestamp;
        }

        const timeSinceLastTick = Math.min(this.timeScale * (timestamp - this.start), 32);

        this.elapsedTime += timeSinceLastTick;
        this.deltaMS = timeSinceLastTick;
        this.start = timestamp;

        this.emit('tick', this.deltaMS);
        this.rafId = requestAnimationFrame((delta) => this.tick(delta));
    }

    /** Stops scheduling new frames and notifies listeners. */
    public pause(): void
    {
        this.paused = true;
        this.emit('paused');
    }

    /** Resumes ticking from the current {@link performance.now} timestamp. */
    public resume(): void
    {
        this.paused = false;
        this.start = performance.now();
        this.run();
        this.emit('resumed');
    }

    /**
     * Adjusts the tick speed multiplier.
     * @param scale
     */
    public setTimeScale(scale: number): void
    {
        this.timeScale = scale;
        if (scale > 0)
        {
            this.previousNonZeroTimeScale = scale;
        }
    }

    /** Stops the RAF loop and clears listeners. */
    public dispose(): void
    {
        if (this.rafId !== undefined)
        {
            cancelAnimationFrame(this.rafId);
            this.rafId = undefined;
        }

        this.removeAllListeners();
        this.paused = true;
    }
}
