import { createAbortHandle } from '../abort-handler.js';
import { asArray, findElementFromEventPath, isFunction, toMerged, } from '../util.js';
export const arrowLeft = 'ArrowLeft';
export const arrowRight = 'ArrowRight';
export const arrowUp = 'ArrowUp';
export const arrowDown = 'ArrowDown';
export const enterKey = 'Enter';
export const spaceBar = ' ';
export const escapeKey = 'Escape';
export const homeKey = 'Home';
export const endKey = 'End';
export const pageUpKey = 'PageUp';
export const pageDownKey = 'PageDown';
export const tabKey = 'Tab';
export const altKey = 'Alt';
export const ctrlKey = 'Ctrl';
export const metaKey = 'Meta';
export const shiftKey = 'Shift';
const Modifiers = new Map([
    [altKey.toLowerCase(), altKey.toLowerCase()],
    [ctrlKey.toLowerCase(), ctrlKey.toLowerCase()],
    [metaKey.toLowerCase(), metaKey.toLowerCase()],
    [shiftKey.toLowerCase(), shiftKey.toLowerCase()],
]);
const ALL_MODIFIER_VALUES = Array.from(Modifiers.values()).sort();
function normalizeKeys(keys) {
    return asArray(keys).map((key) => key.toLowerCase());
}
function isKeydown(event) {
    return event.type === 'keydown';
}
function isKeyup(event) {
    return event.type === 'keyup';
}
function isKeydownTrigger(triggers) {
    return triggers
        ? triggers.includes('keydown') || isKeydownRepeatTrigger(triggers)
        : false;
}
function isKeyupTrigger(triggers) {
    return triggers ? triggers.includes('keyup') : false;
}
function isKeydownRepeatTrigger(triggers) {
    return triggers ? triggers.includes('keydownRepeat') : false;
}
function createCombinationKey(keys, modifiers) {
    const sortedKeys = keys.toSorted();
    const sortedModifiers = ALL_MODIFIER_VALUES.filter((mod) => modifiers.includes(mod)).sort();
    return sortedModifiers.concat(sortedKeys).join('+');
}
class KeyBindingController {
    get _element() {
        if (this._observedElement) {
            return this._observedElement;
        }
        return this._ref?.value || this._host;
    }
    constructor(host, options) {
        this._abortHandle = createAbortHandle();
        this._bindings = new Map();
        this._allowedKeys = new Set();
        this._pressedKeys = new Set();
        this._host = host;
        this._ref = options?.ref;
        this._options = toMerged(KeyBindingController._defaultOptions, options ?? {});
        if (Array.isArray(this._options.skip)) {
            this._skipSelector = this._options.skip.join(',');
        }
        host.addController(this);
    }
    _applyEventModifiers(binding, event) {
        if (binding.options?.preventDefault) {
            event.preventDefault();
        }
        if (binding.options?.stopPropagation) {
            event.stopPropagation();
        }
    }
    _bindingMatches(binding, event) {
        const triggers = binding.options?.triggers ?? ['keydown'];
        if (isKeydown(event) && isKeydownTrigger(triggers)) {
            return true;
        }
        if (isKeyup(event) && isKeyupTrigger(triggers)) {
            return true;
        }
        return false;
    }
    _shouldSkip(event) {
        const skip = this._options?.skip;
        if (!this._allowedKeys.has(event.key.toLowerCase())) {
            return true;
        }
        if (!findElementFromEventPath((e) => e === this._element, event)) {
            return true;
        }
        if (Array.isArray(skip)) {
            if (!this._skipSelector) {
                return false;
            }
            return Boolean(findElementFromEventPath(this._skipSelector, event));
        }
        if (isFunction(skip)) {
            return skip.call(this._host, event.target, event);
        }
        return false;
    }
    hostConnected() {
        const { signal } = this._abortHandle;
        this._host.addEventListener('keyup', this, { signal });
        this._host.addEventListener('keydown', this, { signal });
    }
    hostDisconnected() {
        this._abortHandle.abort();
    }
    handleEvent(event) {
        if (this._shouldSkip(event)) {
            return;
        }
        const key = event.key.toLowerCase();
        if (!Modifiers.has(key)) {
            this._pressedKeys.add(key);
        }
        const activeModifiers = ALL_MODIFIER_VALUES.filter((mod) => event[`${mod}Key`]);
        const combination = createCombinationKey(Array.from(this._pressedKeys), activeModifiers);
        const binding = this._bindings.get(combination);
        if (binding && this._bindingMatches(binding, event)) {
            this._applyEventModifiers(binding, event);
            binding.handler.call(this._host, event);
            if (isKeydownRepeatTrigger(binding.options?.triggers)) {
                this._pressedKeys.delete(key);
            }
        }
        if (isKeyup(event) && !Modifiers.has(key)) {
            this._pressedKeys.delete(key);
        }
    }
    set(key, handler, bindingOptions) {
        const { keys, modifiers } = parseKeys(key);
        const combination = createCombinationKey(keys, modifiers);
        const options = toMerged(this._options.bindingDefaults, bindingOptions ?? {});
        for (const each of [...keys, ...modifiers]) {
            this._allowedKeys.add(each);
        }
        this._bindings.set(combination, { keys, handler, options, modifiers });
        return this;
    }
    setActivateHandler(handler, options) {
        this.set(enterKey, handler, options);
        this.set(spaceBar, handler, options);
        return this;
    }
    observeElement(element) {
        element.addEventListener('keydown', this);
        element.addEventListener('keyup', this);
        this._observedElement = element;
        return {
            unsubscribe: () => {
                this._observedElement?.removeEventListener('keydown', this);
                this._observedElement?.removeEventListener('keyup', this);
                this._observedElement = undefined;
            },
        };
    }
}
KeyBindingController._defaultOptions = {
    skip: ['input', 'textarea', 'select'],
    bindingDefaults: { preventDefault: true },
};
export function parseKeys(keys) {
    const normalizedKeys = normalizeKeys(keys);
    return {
        keys: normalizedKeys.filter((key) => !Modifiers.has(key)),
        modifiers: normalizedKeys.filter((key) => Modifiers.has(key)),
    };
}
export function addKeybindings(element, options) {
    return new KeyBindingController(element, options);
}
//# sourceMappingURL=key-bindings.js.map