import { getRealPoint } from "./dom";
import type { Point } from "./geometry";

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

    released?: boolean;
    pressed?: boolean;
    repeated?: boolean;
    held?: boolean;

    changedAt: number;
    eventAt: number;
    repeatAt?: number;
}
namespace Input {
    export enum KeyCode {
        UP = 'ArrowUp',
        DOWN = 'ArrowDown',
        LEFT = 'ArrowLeft',
        RIGHT = 'ArrowRight',
        SPACE = 'Space',
        SHIFT = 'Shift',
        SHIFT_LEFT = 'ShiftLeft',
        SHIFT_RIGHT = 'ShiftRight',
        A = 'KeyA',
        B = 'KeyB',
        C = 'KeyC',
        D = 'KeyD',
        E = 'KeyE',
        F = 'KeyF',
        G = 'KeyG',
        H = 'KeyH',
        I = 'KeyI',
        J = 'KeyJ',
        K = 'KeyK',
        L = 'KeyL',
        M = 'KeyM',
        N = 'KeyN',
        O = 'KeyO',
        P = 'KeyP',
        Q = 'KeyQ',
        R = 'KeyR',
        S = 'KeyS',
        T = 'KeyT',
        U = 'KeyU',
        V = 'KeyV',
        W = 'KeyW',
        X = 'KeyX',
        Y = 'KeyY',
        Z = 'KeyZ',
        NUM_0 = 'Digit0',
        NUM_1 = 'Digit1',
        NUM_2 = 'Digit2',
        NUM_3 = 'Digit3',
        NUM_4 = 'Digit4',
        NUM_5 = 'Digit5',
        NUM_6 = 'Digit6',
        NUM_7 = 'Digit7',
        NUM_8 = 'Digit8',
        NUM_9 = 'Digit9',

        MOUSE_LEFT = 'Mouse0',
        MOUSE_MIDDLE = 'Mouse1',
        MOUSE_RIGHT = 'Mouse2',
    };

    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<string, IKeyState>> = {};
    const mousePosition: Point = { x: 0, y: 0 };
    let mainCanvas: HTMLCanvasElement | null = null;
    let repeatIntervalMs = 10;
    let repeatDelayMs = 300;

    const onStateChange = (keyId: string, state: boolean) => {
        console.debug(`[Input] ${state ? 'Pressed' : 'Released'} ${keyId}`);
        const now = Date.now();

        if (KEYS[keyId]) {
            if (state !== KEYS[keyId].state) {
                KEYS[keyId].changedAt = now;
            }
            KEYS[keyId].state = state;
            KEYS[keyId].eventAt = now;
        } else {
            KEYS[keyId] = { state, changedAt: now, eventAt: now };
        }

        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);
        }
    };

    const onMouseChange = (e: MouseEvent) => {
        if (mainCanvas) {
            const point = getRealPoint(mainCanvas, e);
            mousePosition.x = point.x;
            mousePosition.y = point.y;
        } else {
            mousePosition.x = e.clientX;
            mousePosition.y = e.clientY;
        }
    }

    document.body.addEventListener('keydown', (e) => onStateChange(e.code as KeyCode, true));
    document.body.addEventListener('keyup', (e) => onStateChange(e.code as KeyCode, false));
    document.body.addEventListener('mousedown', (e) => {
        onStateChange(`Mouse${e.button}`, true);
        onMouseChange(e);
    });
    document.body.addEventListener('mouseup', (e) => {
        onStateChange(`Mouse${e.button}`, false);
        onMouseChange(e);
    });
    document.body.addEventListener('mousemove', (e) => onMouseChange(e));
    document.body.addEventListener('contextmenu', (e) => e.preventDefault());

    export const isPressed = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.pressed);
    export const isReleased = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.released);
    export const isRepeated = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.repeated);
    export const isHeld = (...keys: KeyCode[]) => keys.some(key => KEYS[key]?.held);

    export const hasPressed = () => Object.values(KEYS).some(k => k?.pressed);
    export const hasReleased = () => Object.values(KEYS).some(k => k?.released);
    export const hasRepeated = () => Object.values(KEYS).some(k => k?.repeated);
    export const hasHeld = () => Object.values(KEYS).some(k => k?.held);

    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 const getMousePosition = (): Point => ({ ...mousePosition });

    export const setRepeatInterval = (intervalMs: number) => {
        repeatIntervalMs = intervalMs;
    }

    export const setRepeatDelay = (delayMs: number) => {
        repeatDelayMs = delayMs;
    }

    export const setMainCanvas = (canvas: HTMLCanvasElement | null) => {
        mainCanvas = canvas;
    }

    export function updateKeys() {
        for (const key of Object.values(KEYS)) {
            if (!key) continue;

            key.released = false;
            key.pressed = false;

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

            const now = Date.now();
            key.repeated = key.held && (now - (key.repeatAt ?? 0) > repeatIntervalMs) && (now - key.changedAt > repeatDelayMs);
            if (key.repeated) {
                key.repeatAt = now;
            }
            key.prevState = key.state;
        }
    }
}

export default Input;
