"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Context_1 = __importDefault(require("../Context"));
const utils_1 = require("../utils");
/**
 *
 * A service for adding key listeners. Key events are handled in a custom event handling system, which allows many useful
 * features like listening to DOM document root, fine grained control over the order of event bubbling etc and passing any
 * needed data to the event handler.
 *
 * When initialized, it adds a global keydown listener to the document which handles the LajiForm key shortcuts defined in
 * uiSchema["ui:shortcuts"].
 *
 **/
class KeyhandlerService {
    constructor(formContext) {
        this.shortcuts = {};
        this.keyHandleListeners = {};
        this.keyHandleIdFunctions = [];
        this.blocked = false;
        this.globalEventsRootHandler = {};
        this.globalEventHandlers = {};
        this.addKeyHandler = (id, keyFunctions, additionalParams) => {
            if (!this.keyHandleListeners[id])
                this.keyHandleListeners[id] = [];
            const handleKey = (e) => {
                if (this.blocked) {
                    e.preventDefault();
                    return;
                }
                return this.handleKeysWith(id, keyFunctions, e, additionalParams);
            };
            this.keyHandleIdFunctions.push({ id, keyFunctions, handleKey });
            this.keyHandleListeners[id].push(handleKey);
        };
        this.removeKeyHandler = (_id, _keyFunctions) => {
            this.keyHandleIdFunctions.forEach((idFunction, i) => {
                const { id, keyFunctions, handleKey } = idFunction;
                if (id === _id && _keyFunctions === keyFunctions) {
                    this.keyHandleIdFunctions.splice(i, 1);
                    this.keyHandleListeners[id] = this.keyHandleListeners[id].filter((_handleKey) => _handleKey !== handleKey);
                }
            });
        };
        this.formContext = formContext;
        this.onKeyDown = this.onKeyDown.bind(this);
    }
    initialize() {
        this.addGlobalEventHandler("keydown", this.onKeyDown);
    }
    destroy() {
        this.removeGlobalEventHandler("keydown", this.onKeyDown);
    }
    block() {
        this.blocked = true;
    }
    unblock() {
        this.blocked = false;
    }
    setShortcuts(shortcuts = {}, keyFunctions) {
        this.keyHandleListeners = {};
        this.keyHandleIdFunctions = [];
        this.keyHandlers = this.getKeyHandlers(shortcuts);
        this.addKeyHandler("root", keyFunctions);
        this.keyHandlerTargets = this.keyHandlers.reduce((targets, handler) => {
            if (typeof handler.target === "string")
                targets.push({ id: handler.target, handler });
            return targets;
        }, []);
        this.shortcuts = shortcuts;
    }
    setFormContext(formContext) {
        this.formContext = formContext;
    }
    getKeyHandlers(shortcuts = {}) {
        return Object.keys(shortcuts).reduce((list, keyCombo) => {
            const shortcut = shortcuts[keyCombo];
            const modifiers = {
                alt: false,
                ctrl: false,
                shift: false,
            };
            list.push(keyCombo.split("+").reduce((keyHandler, key) => {
                if (key in modifiers) {
                    modifiers[key] = true;
                }
                keyHandler.conditions.push(e => e.key === key || (key in modifiers && ((modifiers[key] && e[`${key}Key`]) || (!modifiers[key] && !e[`${key}Key`]))));
                return keyHandler;
            }, Object.assign(Object.assign({}, shortcut), { conditions: [] })));
            for (let modifier in modifiers) {
                if (!modifiers[modifier])
                    list[list.length - 1].conditions.push(e => {
                        return !e[`${modifier}Key`];
                    });
            }
            return list;
        }, []);
    }
    onKeyDown(e) {
        const currentId = this.formContext.utils.findNearestParentSchemaElemId(e.target) || "";
        let order = Object.keys(this.keyHandleListeners).filter(id => {
            if (currentId.startsWith(id))
                return true;
            return;
        }).sort((a, b) => {
            return b.length - a.length;
        });
        const targets = this.keyHandlerTargets
            .filter(({ handler }) => handler.conditions.every(condition => condition(e)))
            .map(({ id }) => (0, utils_1.getKeyHandlerTargetId)(id, (0, Context_1.default)(this.formContext.contextId)));
        order = [...targets, ...order];
        order.some(id => { var _a; return (_a = this.keyHandleListeners[id]) === null || _a === void 0 ? void 0 : _a.some((keyHandleListener) => keyHandleListener(e)); });
    }
    /**
     * Global event handlers are listened for the whole DOM document. React can't listen to events on document level, hence this is useful.
     **/
    addGlobalEventHandler(name, fn) {
        if (!this.globalEventHandlers[name]) {
            this.globalEventsRootHandler[name] = e => {
                if (!e.persist)
                    e.persist = () => { };
                const origStopPropagation = e.stopPropagation;
                e.stopPropagation = () => {
                    e._lajiFormStoppedFlag = true;
                    origStopPropagation.call(e);
                };
                this.globalEventHandlers[name].some(h => {
                    if (e._lajiFormStoppedFlag) {
                        return true;
                    }
                    h(e);
                    return false;
                });
            };
            document.addEventListener(name, this.globalEventsRootHandler[name]);
            this.globalEventHandlers[name] = [];
        }
        this.globalEventHandlers[name].push(fn);
    }
    removeGlobalEventHandler(name, fn) {
        this.globalEventHandlers[name] = this.globalEventHandlers[name].filter(_fn => _fn !== fn);
        if (this.globalEventHandlers[name].length === 0) {
            delete this.globalEventHandlers[name];
            document.removeEventListener(name, this.globalEventsRootHandler[name]);
        }
    }
    handleKeysWith(id, keyFunctions = {}, e, additionalParams = {}) {
        if (this.blocked && !(0, utils_1.isDescendant)(document.querySelector(".pass-block"), e.target)) {
            e.preventDefault();
            return;
        }
        if ((0, utils_1.isDescendant)(document.querySelector(".laji-map"), e.target))
            return;
        function handleKey(keyHandler) {
            const returnValue = keyFunctions[keyHandler.fn](e, Object.assign(Object.assign({}, keyHandler), additionalParams));
            const eventHandled = returnValue !== undefined ? returnValue : true;
            if (eventHandled) {
                e.preventDefault();
                e.stopPropagation();
            }
            return eventHandled;
        }
        const highPriorityHandled = this.keyHandlers.some(keyHandler => {
            let target = (0, utils_1.getKeyHandlerTargetId)(keyHandler.target, (0, Context_1.default)(this.formContext.contextId));
            if (keyFunctions[keyHandler.fn] && "target" in keyHandler && id.match(target) && keyHandler.conditions.every(condition => condition(e))) {
                if (!handleKey(keyHandler)) {
                    e.preventDefault();
                    e.stopPropagation();
                }
                return true;
            }
            return false;
        });
        if (highPriorityHandled)
            return highPriorityHandled;
        return this.keyHandlers.some(keyHandler => {
            if (keyFunctions[keyHandler.fn] && keyHandler.conditions.every(condition => condition(e))) {
                return handleKey(keyHandler);
            }
            return false;
        });
    }
}
exports.default = KeyhandlerService;
