interface IKeyState {
    state: boolean;
    prevState?: boolean;

    released?: boolean;
    pressed?: boolean;
    held?: boolean;
}
namespace Input {
    export enum KeyCode {
        UP = 'ArrowUp',
        DOWN = 'ArrowDown',
        LEFT = 'ArrowLeft',
        RIGHT = 'ArrowRight',
        SPACE = 'Space',
        SHIFT = 'Shift',
        SHIFT_LEFT = 'ShiftLeft',
        SHIFT_RIGHT = 'ShiftRight',
    };

    export enum GamepadAxis {
        LX = 0,
        LY = 1,
        RX = 2,
        RY = 3,
    }

    export enum GamepadButton {
        A = 0,
        B = 1,
        X = 2,
        Y = 3,
        LB = 4,
        RB = 5,
        LT = 6,
        RT = 7,
        SELECT = 8,
        START = 9,
        L3 = 10,
        R3 = 11,
        D_UP = 12,
        D_DOWN = 13,
        D_LEFT = 14,
        D_RIGHT = 15,
        HOME = 16,
    }

    const DEAD_ZONE = 0.05;
    const KEYS: Partial<Record<KeyCode, IKeyState>> = {};

    const onStateChange = (keyId: KeyCode, state: boolean) => {
        console.debug(`[Input] ${state ? 'Pressed' : 'Released'} ${keyId}`);
        if (KEYS[keyId]) {
            KEYS[keyId].state = state;
        } else {
            KEYS[keyId] = { state };
        }

        if (keyId === KeyCode.SHIFT_LEFT || keyId === KeyCode.SHIFT_RIGHT) {
            const shiftState = Boolean(KEYS[KeyCode.SHIFT_LEFT]?.state || KEYS[KeyCode.SHIFT_RIGHT]?.state);
            onStateChange(KeyCode.SHIFT, shiftState);
        }
    };

    document.body.addEventListener('keydown', (e) => onStateChange(e.code as KeyCode, true));
    document.body.addEventListener('keyup', (e) => onStateChange(e.code as KeyCode, false));

    export const isPressed = (key: KeyCode): boolean => KEYS[key]?.pressed ?? false;
    export const isReleased = (key: KeyCode): boolean => KEYS[key]?.released ?? false;
    export const isHeld = (key: KeyCode): boolean => KEYS[key]?.held ?? false;

    export const getGamepad = () => navigator.getGamepads().find(g => g != null);

    export const getGamepadAxis = (i: GamepadAxis) => {
        const value = getGamepad()?.axes[i] ?? 0;
        return Math.abs(value) < DEAD_ZONE ? 0 : value;
    }
    export const getGamepadButton = (btn: GamepadButton) => getGamepad()?.buttons[btn]?.value ?? 0;

    export const getLeft = () => isHeld(KeyCode.LEFT) ? 1 : Math.max(0, -getGamepadAxis(GamepadAxis.LX));
    export const getRight = () => isHeld(KeyCode.RIGHT) ? 1 : Math.max(0, getGamepadAxis(GamepadAxis.LX));

    export const getUp = () => isHeld(KeyCode.UP) ? 1 : Math.max(0, -getGamepadAxis(GamepadAxis.LY));
    export const getDown = () => isHeld(KeyCode.DOWN) ? 1 : Math.max(0, getGamepadAxis(GamepadAxis.LY));

    export const getHorizontal = () => (
        (Number(isHeld(KeyCode.LEFT)) - Number(isHeld(KeyCode.RIGHT)))
        + getGamepadAxis(GamepadAxis.LX)
    );

    export const getVertical = () => (
        (Number(isHeld(KeyCode.UP)) - Number(isHeld(KeyCode.DOWN)))
        + getGamepadAxis(GamepadAxis.LY)
    );

    export function updateKeys() {
        for (const key of Object.values(KEYS)) {
            key.released = false;
            key.pressed = false;

            if (key.state) {
                key.pressed = !key.held;
                key.held = true;
            } else {
                key.released = true;
                key.held = false;
            }

            key.prevState = key.state;
        }
    }
}

export default Input;