'use strict';

var EventBus = require('../core/EventBus.js');

/* ────────────────────────────────────────────────────────────────
   InputManager — Unified input across gamepad, keyboard, mouse, touch
──────────────────────────────────────────────────────────────── */
/* ── Constants ────────────────────────────────────────────────── */
const BUTTON_MAP = {
    0: 'A', 1: 'B', 2: 'X', 3: 'Y',
    4: 'LB', 5: 'RB', 6: 'LT', 7: 'RT',
    8: 'Back', 9: 'Start', 10: 'LS', 11: 'RS',
    12: 'DpadUp', 13: 'DpadDown', 14: 'DpadLeft', 15: 'DpadRight',
    16: 'Home',
};
const AXIS_MAP = {
    0: 'LeftX', 1: 'LeftY', 2: 'RightX', 3: 'RightY',
};
/* ── InputManager ─────────────────────────────────────────────── */
class InputManager {
    constructor(deadzone = 0.15) {
        this.deadzone = deadzone;
        this.events = new EventBus.EventBus();
        this._deadzone = 0.15;
        this._actions = new Map();
        // Key state
        this._keys = new Set();
        this._keysJustPressed = new Set();
        this._keysJustReleased = new Set();
        // Mouse state
        this._mousePos = { x: 0, y: 0 };
        this._mouseButtons = new Set();
        this._mouseButtonsJustPressed = new Set();
        this._mouseButtonsJustReleased = new Set();
        this._mouseWheel = 0;
        // Gamepad state (up to 4)
        this._gamepads = [null, null, null, null];
        // Touch state
        this._touches = new Map();
        // DOM cleanup
        this._cleanup = null;
        this._deadzone = deadzone;
    }
    /* ── Lifecycle ──────────────────────────────────────────────── */
    /**
     * Attach DOM event listeners. Call once when engine starts.
     * Pass the target element (usually the canvas container).
     */
    attach(target = window) {
        const onKeyDown = (e) => {
            if (!(e instanceof KeyboardEvent))
                return;
            if (!this._keys.has(e.code)) {
                this._keysJustPressed.add(e.code);
            }
            this._keys.add(e.code);
            this.events.emit('keydown', e.code);
        };
        const onKeyUp = (e) => {
            if (!(e instanceof KeyboardEvent))
                return;
            this._keys.delete(e.code);
            this._keysJustReleased.add(e.code);
            this.events.emit('keyup', e.code);
        };
        const onMouseMove = (e) => {
            if (!(e instanceof MouseEvent))
                return;
            this._mousePos = { x: e.clientX, y: e.clientY };
        };
        const onMouseDown = (e) => {
            if (!(e instanceof MouseEvent))
                return;
            if (!this._mouseButtons.has(e.button)) {
                this._mouseButtonsJustPressed.add(e.button);
            }
            this._mouseButtons.add(e.button);
        };
        const onMouseUp = (e) => {
            if (!(e instanceof MouseEvent))
                return;
            this._mouseButtons.delete(e.button);
            this._mouseButtonsJustReleased.add(e.button);
        };
        const onWheel = (e) => {
            if (!(e instanceof WheelEvent))
                return;
            this._mouseWheel += e.deltaY;
        };
        const onTouchStart = (e) => {
            if (!(e instanceof TouchEvent))
                return;
            for (let i = 0; i < e.changedTouches.length; i++) {
                const t = e.changedTouches[i];
                this._touches.set(t.identifier, { x: t.clientX, y: t.clientY });
            }
        };
        const onTouchMove = (e) => {
            if (!(e instanceof TouchEvent))
                return;
            for (let i = 0; i < e.changedTouches.length; i++) {
                const t = e.changedTouches[i];
                this._touches.set(t.identifier, { x: t.clientX, y: t.clientY });
            }
        };
        const onTouchEnd = (e) => {
            if (!(e instanceof TouchEvent))
                return;
            for (let i = 0; i < e.changedTouches.length; i++) {
                this._touches.delete(e.changedTouches[i].identifier);
            }
        };
        const onGamepadConnected = (e) => {
            if (!(e instanceof GamepadEvent))
                return;
            this.events.emit('gamepad:connected', e.gamepad.index);
        };
        const onGamepadDisconnected = (e) => {
            if (!(e instanceof GamepadEvent))
                return;
            this._gamepads[e.gamepad.index] = null;
            this.events.emit('gamepad:disconnected', e.gamepad.index);
        };
        target.addEventListener('keydown', onKeyDown);
        target.addEventListener('keyup', onKeyUp);
        target.addEventListener('mousemove', onMouseMove);
        target.addEventListener('mousedown', onMouseDown);
        target.addEventListener('mouseup', onMouseUp);
        target.addEventListener('wheel', onWheel);
        target.addEventListener('touchstart', onTouchStart);
        target.addEventListener('touchmove', onTouchMove);
        target.addEventListener('touchend', onTouchEnd);
        target.addEventListener('touchcancel', onTouchEnd);
        window.addEventListener('gamepadconnected', onGamepadConnected);
        window.addEventListener('gamepaddisconnected', onGamepadDisconnected);
        this._cleanup = () => {
            target.removeEventListener('keydown', onKeyDown);
            target.removeEventListener('keyup', onKeyUp);
            target.removeEventListener('mousemove', onMouseMove);
            target.removeEventListener('mousedown', onMouseDown);
            target.removeEventListener('mouseup', onMouseUp);
            target.removeEventListener('wheel', onWheel);
            target.removeEventListener('touchstart', onTouchStart);
            target.removeEventListener('touchmove', onTouchMove);
            target.removeEventListener('touchend', onTouchEnd);
            target.removeEventListener('touchcancel', onTouchEnd);
            window.removeEventListener('gamepadconnected', onGamepadConnected);
            window.removeEventListener('gamepaddisconnected', onGamepadDisconnected);
        };
    }
    detach() {
        var _a;
        (_a = this._cleanup) === null || _a === void 0 ? void 0 : _a.call(this);
        this._cleanup = null;
    }
    /* ── Actions ────────────────────────────────────────────────── */
    /** Define an action binding. */
    defineAction(name, binding) {
        this._actions.set(name, binding);
    }
    /** Define multiple action bindings at once. */
    defineActions(actions) {
        for (const [name, binding] of Object.entries(actions)) {
            this._actions.set(name, binding);
        }
    }
    /** Load a preset (e.g. platformer, fps, rpg). */
    loadPreset(preset) {
        this.defineActions(preset.actions);
    }
    /** Query an action state for a specific player. */
    getAction(name, playerIndex = 0) {
        var _a;
        const binding = this._actions.get(name);
        if (!binding) {
            return { pressed: false, justPressed: false, justReleased: false, value: 0 };
        }
        let pressed = false;
        let justPressed = false;
        let justReleased = false;
        let value = 0;
        // Keyboard (player 0 or all)
        if (binding.keys && playerIndex === 0) {
            for (const key of binding.keys) {
                if (this._keys.has(key)) {
                    pressed = true;
                    value = 1;
                }
                if (this._keysJustPressed.has(key))
                    justPressed = true;
                if (this._keysJustReleased.has(key))
                    justReleased = true;
            }
        }
        // Mouse (player 0 only)
        if (binding.mouseButtons && playerIndex === 0) {
            for (const btn of binding.mouseButtons) {
                if (this._mouseButtons.has(btn)) {
                    pressed = true;
                    value = 1;
                }
                if (this._mouseButtonsJustPressed.has(btn))
                    justPressed = true;
                if (this._mouseButtonsJustReleased.has(btn))
                    justReleased = true;
            }
        }
        // Gamepad
        const gp = this._gamepads[playerIndex];
        if (gp) {
            if (binding.gamepadButtons) {
                for (const btn of binding.gamepadButtons) {
                    if (gp.buttons[btn]) {
                        pressed = true;
                        value = 1;
                    }
                    if (gp.buttonsJustPressed.has(btn))
                        justPressed = true;
                    if (gp.buttonsJustReleased.has(btn))
                        justReleased = true;
                }
            }
            if (binding.gamepadAxis) {
                const axisVal = (_a = gp.axes[binding.gamepadAxis.axis]) !== null && _a !== void 0 ? _a : 0;
                const dir = binding.gamepadAxis.direction;
                if ((dir === 'positive' && axisVal > this._deadzone) ||
                    (dir === 'negative' && axisVal < -this._deadzone)) {
                    pressed = true;
                    value = Math.abs(axisVal);
                }
            }
        }
        return { pressed, justPressed, justReleased, value };
    }
    /* ── Direct queries ─────────────────────────────────────────── */
    isKeyDown(code) {
        return this._keys.has(code);
    }
    isKeyJustPressed(code) {
        return this._keysJustPressed.has(code);
    }
    get mousePosition() {
        return this._mousePos;
    }
    get mouseWheelDelta() {
        return this._mouseWheel;
    }
    isMouseDown(button = 0) {
        return this._mouseButtons.has(button);
    }
    getGamepadAxis(playerIndex, axis) {
        var _a, _b;
        return (_b = (_a = this._gamepads[playerIndex]) === null || _a === void 0 ? void 0 : _a.axes[axis]) !== null && _b !== void 0 ? _b : 0;
    }
    getGamepadButton(playerIndex, button) {
        var _a, _b;
        return (_b = (_a = this._gamepads[playerIndex]) === null || _a === void 0 ? void 0 : _a.buttons[button]) !== null && _b !== void 0 ? _b : false;
    }
    isGamepadConnected(index) {
        return this._gamepads[index] != null;
    }
    get connectedGamepads() {
        return this._gamepads.filter(g => g != null).length;
    }
    /** Vibrate a gamepad. */
    vibrate(playerIndex, duration, weak = 0.5, strong = 0.5) {
        var _a;
        const gpRaw = (_a = navigator.getGamepads) === null || _a === void 0 ? void 0 : _a.call(navigator)[playerIndex];
        if (gpRaw === null || gpRaw === void 0 ? void 0 : gpRaw.vibrationActuator) {
            gpRaw.vibrationActuator.playEffect('dual-rumble', {
                duration,
                weakMagnitude: weak,
                strongMagnitude: strong,
            }).catch(() => { });
        }
    }
    /* ── Update (call once per frame BEFORE game logic) ─────────── */
    update() {
        // Poll gamepads
        this.pollGamepads();
        // Clear frame-edge states at end (so they're available during game update)
    }
    /** Clear frame-edge states. Call AFTER game logic. */
    lateUpdate() {
        this._keysJustPressed.clear();
        this._keysJustReleased.clear();
        this._mouseButtonsJustPressed.clear();
        this._mouseButtonsJustReleased.clear();
        this._mouseWheel = 0;
        for (const gp of this._gamepads) {
            if (gp) {
                gp.buttonsJustPressed.clear();
                gp.buttonsJustReleased.clear();
            }
        }
    }
    pollGamepads() {
        var _a;
        const rawPads = (_a = navigator.getGamepads) === null || _a === void 0 ? void 0 : _a.call(navigator);
        if (!rawPads)
            return;
        for (let i = 0; i < 4; i++) {
            const raw = rawPads[i];
            if (!raw) {
                this._gamepads[i] = null;
                continue;
            }
            const prev = this._gamepads[i];
            const state = prev !== null && prev !== void 0 ? prev : createGamepadState();
            // Buttons
            for (let b = 0; b < raw.buttons.length; b++) {
                const btn = BUTTON_MAP[b];
                if (!btn)
                    continue;
                const wasPressed = state.buttons[btn];
                const isPressed = raw.buttons[b].pressed || raw.buttons[b].value > 0.5;
                state.buttons[btn] = isPressed;
                if (isPressed && !wasPressed)
                    state.buttonsJustPressed.add(btn);
                if (!isPressed && wasPressed)
                    state.buttonsJustReleased.add(btn);
            }
            // Axes (with deadzone)
            for (let a = 0; a < raw.axes.length; a++) {
                const axis = AXIS_MAP[a];
                if (!axis)
                    continue;
                let val = raw.axes[a];
                if (Math.abs(val) < this._deadzone)
                    val = 0;
                state.axes[axis] = val;
            }
            state.connected = true;
            state.id = raw.id;
            this._gamepads[i] = state;
        }
    }
}
function createGamepadState() {
    const buttons = {};
    for (const btn of Object.values(BUTTON_MAP))
        buttons[btn] = false;
    const axes = {};
    for (const ax of Object.values(AXIS_MAP))
        axes[ax] = 0;
    return {
        connected: false,
        id: '',
        buttons,
        axes,
        buttonsJustPressed: new Set(),
        buttonsJustReleased: new Set(),
    };
}
/* ── Input Presets ────────────────────────────────────────────── */
const PRESET_PLATFORMER = {
    name: 'Platformer',
    actions: {
        moveLeft: { keys: ['ArrowLeft', 'KeyA'], gamepadButtons: ['DpadLeft'], gamepadAxis: { axis: 'LeftX', direction: 'negative' } },
        moveRight: { keys: ['ArrowRight', 'KeyD'], gamepadButtons: ['DpadRight'], gamepadAxis: { axis: 'LeftX', direction: 'positive' } },
        jump: { keys: ['Space', 'ArrowUp', 'KeyW'], gamepadButtons: ['A'] },
        attack: { keys: ['KeyZ', 'KeyJ'], gamepadButtons: ['X'] },
        special: { keys: ['KeyX', 'KeyK'], gamepadButtons: ['Y'] },
        dash: { keys: ['ShiftLeft'], gamepadButtons: ['RB'] },
        pause: { keys: ['Escape'], gamepadButtons: ['Start'] },
    },
};
const PRESET_TOPDOWN = {
    name: 'Top-Down',
    actions: {
        moveLeft: { keys: ['ArrowLeft', 'KeyA'], gamepadAxis: { axis: 'LeftX', direction: 'negative' } },
        moveRight: { keys: ['ArrowRight', 'KeyD'], gamepadAxis: { axis: 'LeftX', direction: 'positive' } },
        moveUp: { keys: ['ArrowUp', 'KeyW'], gamepadAxis: { axis: 'LeftY', direction: 'negative' } },
        moveDown: { keys: ['ArrowDown', 'KeyS'], gamepadAxis: { axis: 'LeftY', direction: 'positive' } },
        aimX: { gamepadAxis: { axis: 'RightX', direction: 'positive' } },
        aimY: { gamepadAxis: { axis: 'RightY', direction: 'positive' } },
        shoot: { keys: ['Space', 'KeyZ'], gamepadButtons: ['RT'], mouseButtons: [0] },
        interact: { keys: ['KeyE', 'Enter'], gamepadButtons: ['A'] },
        pause: { keys: ['Escape'], gamepadButtons: ['Start'] },
    },
};
const PRESET_TWIN_STICK = {
    name: 'Twin-Stick Shooter',
    actions: {
        moveLeft: { keys: ['KeyA'], gamepadAxis: { axis: 'LeftX', direction: 'negative' } },
        moveRight: { keys: ['KeyD'], gamepadAxis: { axis: 'LeftX', direction: 'positive' } },
        moveUp: { keys: ['KeyW'], gamepadAxis: { axis: 'LeftY', direction: 'negative' } },
        moveDown: { keys: ['KeyS'], gamepadAxis: { axis: 'LeftY', direction: 'positive' } },
        aimLeft: { keys: ['ArrowLeft'], gamepadAxis: { axis: 'RightX', direction: 'negative' } },
        aimRight: { keys: ['ArrowRight'], gamepadAxis: { axis: 'RightX', direction: 'positive' } },
        aimUp: { keys: ['ArrowUp'], gamepadAxis: { axis: 'RightY', direction: 'negative' } },
        aimDown: { keys: ['ArrowDown'], gamepadAxis: { axis: 'RightY', direction: 'positive' } },
        shoot: { keys: ['Space'], gamepadButtons: ['RT'] },
        special: { keys: ['KeyQ'], gamepadButtons: ['LT'] },
        dash: { keys: ['ShiftLeft'], gamepadButtons: ['LB'] },
        pause: { keys: ['Escape'], gamepadButtons: ['Start'] },
    },
};

exports.InputManager = InputManager;
exports.PRESET_PLATFORMER = PRESET_PLATFORMER;
exports.PRESET_TOPDOWN = PRESET_TOPDOWN;
exports.PRESET_TWIN_STICK = PRESET_TWIN_STICK;
//# sourceMappingURL=InputManager.js.map
