import { Clock } from 'three'

import { type ITime } from './engine_types.js';
import { getParam } from './engine_utils.js';

const timescaleUrl = getParam("timescale");
let timeScale = 1;
if (typeof timescaleUrl === "number") timeScale = timescaleUrl;

/**
 * Provides time-related information for frame-based game logic.  
 * Access via `this.context.time` from any component.  
 *
 * @example Using deltaTime for frame-rate independent movement
 * ```ts
 * update() {
 *   // Move 1 unit per second regardless of frame rate
 *   this.gameObject.position.x += 1 * this.context.time.deltaTime;
 * }
 * ```
 * @example Checking elapsed time
 * ```ts
 * start() {
 *   console.log(`Time since start: ${this.context.time.time}s`);
 *   console.log(`Current frame: ${this.context.time.frameCount}`);
 * }
 * ```
 */
export class Time implements ITime {

    /** The time in seconds since the start of Needle Engine. */
    get time() { return this._time; }
    private set time(value: number) { this._time = value; }
    private _time = 0;

    /** The time in seconds it took to complete the last frame (Read Only). */
    get deltaTime() { return this._deltaTime; }
    private set deltaTime(value: number) { this._deltaTime = value; }
    private _deltaTime = 0;

    /** The time in seconds it took to complete the last frame (Read Only). Timescale is not applied. */
    get deltaTimeUnscaled() { return this._deltaTimeUnscaled; }
    private _deltaTimeUnscaled = 0;

    /**
     * The scale at which time passes. Default is 1.
     * - Values < 1 create slow motion (e.g. 0.5 = half speed)
     * - Values > 1 speed up time (e.g. 2 = double speed)
     * - Value of 0 effectively pauses time-dependent logic
     */
    timeScale = 1;

    /** same as frameCount */
    get frame() { return this._frame; }
    private set frame(value: number) { this._frame = value; }
    private _frame = 0;
    /** The total number of frames that have passed (Read Only). Same as frame */
    get frameCount() { return this.frame; }

    /** The time in seconds it took to complete the last frame (Read Only). */
    get realtimeSinceStartup(): number {
        return this.clock.elapsedTime;
    }

    /** 
     * @returns {Number} FPS for this frame.   
     * Note that this returns the raw value (e.g. 59.88023952362959) and will fluctuate a lot between frames.  
     * If you want a more stable FPS, use `smoothedFps` instead.
    */
    get fps() {
        return 1 / this.deltaTime;
    }

    /** 
     * Approximated frames per second   
     * @returns the smoothed FPS value over the last 60 frames with decimals.  
    */
    get smoothedFps() { return this._smoothedFps; }
    /** The smoothed time in seconds it took to complete the last frame (Read Only). */
    get smoothedDeltaTime() { return 1 / this._smoothedFps; }


    private clock = new Clock();
    private _smoothedFps: number = 0;
    private _smoothedDeltaTime: number = 0;
    private readonly _fpsSamples: number[] = [];
    private _fpsSampleIndex: number = 0;

    constructor() {
        if (typeof timeScale === "number")
            this.timeScale = timeScale;
    }

    /** Step the time. This is called automatically by the Needle Engine Context.
     * @internal
     */
    update() {
        this.deltaTime = this.clock.getDelta();
        // clamp delta time because if tab is not active clock.getDelta can get pretty big
        this.deltaTime = Math.min(.1, this.deltaTime);
        this._deltaTimeUnscaled = this.deltaTime;
        if (this.deltaTime <= 0) this.deltaTime = 0.000000000001;
        this.deltaTime *= this.timeScale;
        this.frame += 1;
        this.time += this.deltaTime;

        if (this._fpsSamples.length < 60) this._fpsSamples.push(this.deltaTime);
        else this._fpsSamples[(this._fpsSampleIndex++) % 60] = this.deltaTime;
        let sum = 0;
        for (let i = 0; i < this._fpsSamples.length; i++)
            sum += this._fpsSamples[i];
        this._smoothedDeltaTime = sum / this._fpsSamples.length;
        this._smoothedFps = 1 / this._smoothedDeltaTime;
    }
}