import { formatError, formatErrorMessage } from "./errors";
import Input from "./input";
import { nextFrame } from "./utils";

type Setup<T> = () => Promise<T> | T;
type Frame<T> = (dt: number, state: T) => Promise<T | void> | T | void;
type GameMain = () => void;

export function gameLoop<T>(frame: Frame<T>): GameMain;
export function gameLoop<T>(setup: Setup<T>, frame: Frame<T>): GameMain;
export function gameLoop<T>(setupOrFrame: Setup<T> | Frame<T>, frame?: Frame<T>): GameMain {
    return async () => {
        let state: T;
        try {
            if (frame) {
                state = await (setupOrFrame as Setup<T>)();
            } else {
                frame = setupOrFrame as Frame<T>;
                state = {} as T;
            }
        } catch (e) {
            console.error(formatError(e, 'Error in game setup'));
            alert(formatErrorMessage(e));
            return;
        }

        try {
            let prevFrame = performance.now();
            while (true) {
                await nextFrame();
                Input.updateKeys();

                const now = performance.now();
                const dt = (now - prevFrame) / 1000;

                if (dt < 1) {
                    const newState = await frame(dt, state);
                    if (newState) {
                        state = newState;
                    }
                }

                prevFrame = performance.now();
            }
        } catch (e) {
            console.error(formatError(e, 'Error in game loop'));
            alert(formatErrorMessage(e));
            return;
        }
    }
};
