import React, { PropsWithChildren, createContext, useCallback, useMemo, useRef, useState } from "react";
import { GameStatus, GameStatusChangeEvent } from "../types";

export type GameContextValue = {
    readonly reset: () => void;
    readonly start: () => void;
    readonly pause: () => void;
    readonly stop: () => void;
    readonly timeout: number;
    readonly status: GameStatus;
    readonly startTime: number | null;
    readonly pauseTime: number;
    readonly endTime: number | null;
};

export type GameContextProviderEvents = {
    onStart?: (event: GameStatusChangeEvent) => unknown;
    onStop?: (event: GameStatusChangeEvent) => unknown;
    onPause?: (event: GameStatusChangeEvent) => unknown;
    onTimedOut?: (event: GameStatusChangeEvent) => unknown;
    onReset?: (event: GameStatusChangeEvent) => unknown;
};

export const GameContext = createContext<GameContextValue>({
    reset: () => {},
    pause: () => {},
    start: () => {},
    stop: () => {},
    timeout: 0,
    status: GameStatus.READY,
    startTime: null,
    pauseTime: 0,
    endTime: null
});

export type GameContextProviderProps = PropsWithChildren & {
    timeout?: number;
    events?: GameContextProviderEvents;
};

export const GameContextProvider: React.FC<GameContextProviderProps> = ({
    children,
    timeout: inputTimeout = 0,
    events
}) => {
    const timerRef = useRef<NodeJS.Timeout>(null);
    const timeout = Math.max(0, inputTimeout);

    const [status, setStatus] = useState(GameStatus.READY);
    const [startTime, setStartTime] = useState<number | null>(null);
    const [endTime, setEndTime] = useState<number | null>(null);
    const [pauseTime, setPauseTime] = useState<number>(0);
    const [pausedAtTime, setPausedAtTime] = useState<number | null>(null);

    const onTimedOut = useCallback(() => {
        const newEndTime = +new Date();
        const newStatus = GameStatus.TIMEDOUT;

        setEndTime(newEndTime);
        setStatus(GameStatus.TIMEDOUT);

        events?.onTimedOut?.({
            startTime,
            endTime: newEndTime,
            status: newStatus,
            pausedAtTime: null,
            pauseTime
        });
    }, [events, pauseTime, startTime]);

    const start = useCallback(() => {
        const newStartTime = startTime || +new Date();
        const newStatus = GameStatus.IN_PROGRESS;
        const newEndTime = null;
        const newPauseTime = pausedAtTime ? pauseTime + +new Date() - (pausedAtTime || 0) : 0;

        setStatus(newStatus);
        setStartTime((currentStartTime) => currentStartTime || newStartTime);
        setPauseTime(newPauseTime);
        setPausedAtTime(null);
        setEndTime(newEndTime);

        if (timeout !== 0) {
            timerRef.current = setTimeout(() => {
                onTimedOut();
            }, timeout * 1000);
        }

        events?.onStart?.({
            startTime: newStartTime,
            endTime: newEndTime,
            status: newStatus,
            pausedAtTime: null,
            pauseTime: newPauseTime
        });
    }, [events, onTimedOut, pauseTime, pausedAtTime, startTime, timeout]);

    const reset = useCallback(() => {
        const newStartTime = null;
        const newStatus = GameStatus.READY;
        const newEndTime = null;
        const newPausedAtTime = null;
        const newPauseTime = 0;

        setStatus(newStatus);
        setStartTime(newStartTime);
        setEndTime(newEndTime);
        setPauseTime(newPauseTime);
        setPausedAtTime(newPausedAtTime);

        if (timerRef.current) {
            clearTimeout(timerRef.current);
        }

        events?.onReset?.({
            startTime: newStartTime,
            endTime: newEndTime,
            status: newStatus,
            pausedAtTime: newPausedAtTime,
            pauseTime: newPauseTime
        });
    }, [events]);

    const stop = useCallback(() => {
        const newStatus = GameStatus.COMPLETED;
        const newEndTime = +new Date();

        if (timerRef.current) {
            clearTimeout(timerRef.current);
        }
        setEndTime(+new Date());
        setStatus(GameStatus.COMPLETED);

        events?.onStop?.({
            startTime,
            endTime: newEndTime,
            status: newStatus,
            pausedAtTime,
            pauseTime
        });
    }, [events, pauseTime, pausedAtTime, startTime]);

    const pause = useCallback(() => {
        const newStatus = GameStatus.PAUSED;
        const newPausedAtTime = +new Date();

        if (timerRef.current) {
            clearTimeout(timerRef.current);
        }
        setStatus(GameStatus.PAUSED);
        setPausedAtTime(newPausedAtTime);

        events?.onPause?.({
            startTime,
            endTime: null,
            pausedAtTime: newPausedAtTime,
            status: newStatus,
            pauseTime
        });
    }, [events, pauseTime, startTime]);

    const contextValue = useMemo(
        () => ({
            status,
            startTime,
            endTime,
            pauseTime,
            timeout,
            stop,
            start,
            reset,
            pause
        }),
        [timeout, status, startTime, endTime, pauseTime, pause, reset, start, stop]
    );

    return <GameContext.Provider value={contextValue}>{children}</GameContext.Provider>;
};
