import {
    EventEmitter,
    Listener,
    deleteWith,
    flipBinary
} from '@motajs/legacy-common';
import { KeyCode } from '@motajs/client-base';
import { gameKey } from './hotkey';
import { unwarpBinary } from './hotkey';
import { cloneDeep } from 'lodash-es';
import { shallowReactive } from 'vue';

export interface KeyboardEmits {
    key: KeyCode;
    assist: number;
}

interface KeyboardItem {
    key: KeyCode;
    text?: string;
    x: number;
    y: number;
    width: number;
    height: number;
}

interface VirtualKeyEmit {
    preventDefault(): void;
    preventAssist(): void;
}

type VirtualKeyEmitFn = (
    item: KeyboardItem,
    assist: number,
    index: number,
    ev: VirtualKeyEmit
) => void;

interface VirtualKeyboardEvent {
    add: (item: KeyboardItem) => void;
    remove: (item: KeyboardItem) => void;
    extend: (extended: Keyboard) => void;
    emit: VirtualKeyEmitFn;
    scopeCreate: (scope: symbol) => void;
    scopeDispose: (scope: symbol) => void;
}

/**
 * 虚拟按键，同一个虚拟按键实例应当只能同时操作一个，但可以显示多个
 */
export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
    static list: Keyboard[] = [];

    id: string;
    keys: KeyboardItem[] = [];
    assist: number = 0;
    fontSize: number = 18;

    scope: symbol = Symbol();
    private scopeStack: symbol[] = [];
    private onEmitKey: Record<symbol, Listener<VirtualKeyEmitFn>[]> = {};
    private scopeAssist: Record<symbol, number> = {};

    constructor(id: string) {
        super();
        this.id = id;
        Keyboard.list.push(this);
    }

    /**
     * 给虚拟键盘添加一个按键
     * @param item 按键信息
     */
    add(item: KeyboardItem) {
        const i = shallowReactive(item);
        this.keys.push(i);
        this.emit('add', i);
        return this;
    }

    /**
     * 移除一个按键
     * @param item 按键信息
     */
    remove(item: KeyboardItem) {
        deleteWith(this.keys, item);
        this.emit('remove', item);
        return this;
    }

    /**
     * 创造一个在某些辅助按键已经按下的情况下的作用域，这些被按下的辅助按键还可以被玩家手动取消
     * @param assist 辅助按键
     */
    withAssist(assist: number) {
        const symbol = this.createScope();
        this.assist = assist;
        return symbol;
    }

    /**
     * 创造一个虚拟按键作用域，所有监听的事件与其他作用域不冲突
     * @returns 作用域的唯一标识符
     */
    createScope() {
        const last = this.scopeStack.at(-1);
        const symbol = Symbol();
        this.scope = symbol;
        this.scopeStack.push(symbol);
        const ev: Listener<VirtualKeyEmitFn>[] = [];
        this.onEmitKey[symbol] = ev;
        // @ts-expect-error 无法推导
        this.events = ev;
        this.emit('scopeCreate', symbol);
        if (last) {
            this.scopeAssist[symbol] = this.assist;
        }
        this.assist = 0;
        this.scopeAssist[symbol] = 0;
        return symbol;
    }

    /**
     * 释放一个作用域，同时删除其中的所有监听器
     */
    disposeScope() {
        if (this.scopeStack.length === 0) {
            throw new Error(
                `Cannot dispose virtual key scope since there's no scope to be disposed.`
            );
        }
        const now = this.scopeStack.pop()!;
        delete this.onEmitKey[now];
        delete this.scopeAssist[now];
        const symbol = this.scopeStack.at(-1);
        this.emit('scopeDispose', now);
        if (!symbol) return;
        this.scope = symbol;
        this.assist = this.scopeAssist[symbol];
        // @ts-expect-error 无法推导
        this.events = this.onEmitKey[symbol];
    }

    /**
     * 继承一个按键的按键信息
     * @param keyboard 要被继承的按键
     * @param offsetX 被继承的按键的横坐标偏移量
     * @param offsetY 被继承的按键的纵坐标偏移量
     */
    extend(keyboard: Keyboard, offsetX: number = 0, offsetY: number = 0) {
        const toClone = cloneDeep(keyboard.keys);
        toClone.forEach(v => {
            v.x += offsetX;
            v.y += offsetY;
        });

        this.keys.push(...toClone);
        this.emit('extend', keyboard);
        return this;
    }

    /**
     * 触发按键
     * @param key 要触发的按键
     */
    emitKey(key: KeyboardItem, index: number) {
        let prevent = false;
        let preventAss = false;
        const preventDefault = () => (prevent = true);
        const preventAssist = () => (preventAss = true);
        this.emit('emit', key, this.assist, index, {
            preventDefault,
            preventAssist
        });

        if (!preventAss) {
            if (key.key === KeyCode.Ctrl) {
                this.assist = flipBinary(this.assist, 0);
            } else if (key.key === KeyCode.Shift) {
                this.assist = flipBinary(this.assist, 1);
            } else if (key.key === KeyCode.Alt) {
                this.assist = flipBinary(this.assist, 2);
            }
        }
        if (!prevent) {
            const ev = generateKeyboardEvent(key.key, this.assist);
            gameKey.emitKey(key.key, this.assist, 'up', ev);
        }
    }

    static get(id: string) {
        return this.list.find(v => v.id === id);
    }
}

export function generateKeyboardEvent(_key: KeyCode, assist: number) {
    const { ctrl, alt, shift } = unwarpBinary(assist);
    const ev = new KeyboardEvent('keyup', {
        ctrlKey: ctrl,
        shiftKey: shift,
        altKey: alt
    });

    return ev;
}
