import * as tslib_1 from "tslib";
import * as React from 'react';
import { getLayerStyles } from './KeytipLayer.styles';
import { Keytip } from '../../Keytip';
import { Layer } from '../../Layer';
import { BaseComponent, classNamesFunction, getDocument, arraysEqual } from '../../Utilities';
import { KeytipManager } from '../../utilities/keytips/KeytipManager';
import { KeytipTree } from './KeytipTree';
import { ktpTargetFromId, ktpTargetFromSequences, sequencesToID, mergeOverflows } from '../../utilities/keytips/KeytipUtils';
import { transitionKeysContain, KeytipTransitionModifier } from '../../utilities/keytips/IKeytipTransitionKey';
import { KeytipEvents, KTP_LAYER_ID, KTP_ARIA_SEPARATOR } from '../../utilities/keytips/KeytipConstants';
var isMac = typeof navigator !== 'undefined' && navigator.userAgent.indexOf('Macintosh') >= 0;
// Default sequence is Alt-Windows (Alt-Meta) in Windows, Option-Control (Alt-Control) in Mac
var defaultStartSequence = {
    key: isMac ? 'Control' : 'Meta',
    modifierKeys: [KeytipTransitionModifier.alt]
};
// Default exit sequence is the same as the start sequence
var defaultExitSequence = defaultStartSequence;
// Default return sequence is Escape
var defaultReturnSequence = {
    key: 'Escape'
};
var getClassNames = classNamesFunction();
/**
 * A layer that holds all keytip items
 *
 * @export
 * @class KeytipLayer
 * @extends {BaseComponent<IKeytipLayerProps>}
 */
var KeytipLayerBase = /** @class */ (function (_super) {
    tslib_1.__extends(KeytipLayerBase, _super);
    // tslint:disable-next-line:no-any
    function KeytipLayerBase(props, context) {
        var _this = _super.call(this, props, context) || this;
        _this._keytipManager = KeytipManager.getInstance();
        _this._delayedKeytipQueue = [];
        _this._keyHandled = false;
        _this._onDismiss = function (ev) {
            // if we are in keytip mode, then exit keytip mode
            if (_this.state.inKeytipMode) {
                _this._exitKeytipMode(ev);
            }
        };
        _this._onKeyDown = function (ev) {
            _this._keyHandled = false;
            // using key since which has been deprecated and key is now widely suporrted.
            // See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/which
            var key = ev.key;
            switch (key) {
                case 'Alt':
                    // ALT puts focus in the browser bar, so it should not be used as a key for keytips.
                    // It can be used as a modifier
                    break;
                case 'Tab':
                case 'Enter':
                case 'Spacebar':
                case ' ':
                case 'ArrowUp':
                case 'Up':
                case 'ArrowDown':
                case 'Down':
                case 'ArrowLeft':
                case 'Left':
                case 'ArrowRight':
                case 'Right':
                    if (_this.state.inKeytipMode) {
                        _this._keyHandled = true;
                        _this._exitKeytipMode(ev);
                    }
                    break;
                default:
                    // Special cases for browser-specific keys that are not at standard
                    // (according to http://www.w3.org/TR/uievents-key/#keys-navigation)
                    if (key === 'Esc') {
                        // Edge: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/5290772/
                        key = 'Escape';
                    }
                    else if (key === 'OS' || key === 'Win') {
                        // Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
                        // Edge: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
                        // and https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16424492/
                        key = 'Meta';
                    }
                    var transitionKey = { key: key };
                    transitionKey.modifierKeys = _this._getModifierKey(key, ev);
                    _this.processTransitionInput(transitionKey, ev);
                    break;
            }
        };
        _this._onKeyPress = function (ev) {
            if (_this.state.inKeytipMode && !_this._keyHandled) {
                // Call processInput
                _this.processInput(ev.key.toLocaleLowerCase(), ev);
                ev.preventDefault();
                ev.stopPropagation();
            }
        };
        _this._onKeytipAdded = function (eventArgs) {
            var keytipProps = eventArgs.keytip;
            var uniqueID = eventArgs.uniqueID;
            _this._keytipTree.addNode(keytipProps, uniqueID);
            _this._setKeytips();
            // Add the keytip to the queue to show later
            if (_this._keytipTree.isCurrentKeytipParent(keytipProps)) {
                _this._addKeytipToQueue(sequencesToID(keytipProps.keySequences));
            }
            if (_this._newCurrentKeytipSequences && arraysEqual(keytipProps.keySequences, _this._newCurrentKeytipSequences)) {
                _this._triggerKeytipImmediately(keytipProps);
            }
            if (_this._isCurrentKeytipAnAlias(keytipProps)) {
                var keytipSequence = keytipProps.keySequences;
                if (keytipProps.overflowSetSequence) {
                    keytipSequence = mergeOverflows(keytipSequence, keytipProps.overflowSetSequence);
                }
                _this._keytipTree.currentKeytip = _this._keytipTree.getNode(sequencesToID(keytipSequence));
            }
        };
        _this._onKeytipUpdated = function (eventArgs) {
            var keytipProps = eventArgs.keytip;
            var uniqueID = eventArgs.uniqueID;
            _this._keytipTree.updateNode(keytipProps, uniqueID);
            _this._setKeytips();
        };
        _this._onKeytipRemoved = function (eventArgs) {
            var keytipProps = eventArgs.keytip;
            var uniqueID = eventArgs.uniqueID;
            // Remove keytip from the delayed queue
            _this._removeKeytipFromQueue(sequencesToID(keytipProps.keySequences));
            // Remove the node from the Tree
            _this._keytipTree.removeNode(keytipProps, uniqueID);
            _this._setKeytips();
        };
        _this._onPersistedKeytipAdded = function (eventArgs) {
            var keytipProps = eventArgs.keytip;
            var uniqueID = eventArgs.uniqueID;
            _this._keytipTree.addNode(keytipProps, uniqueID, true);
        };
        _this._onPersistedKeytipRemoved = function (eventArgs) {
            var keytipProps = eventArgs.keytip;
            var uniqueID = eventArgs.uniqueID;
            _this._keytipTree.removeNode(keytipProps, uniqueID);
        };
        _this._onPersistedKeytipExecute = function (eventArgs) {
            _this._persistedKeytipExecute(eventArgs.overflowButtonSequences, eventArgs.keytipSequences);
        };
        /**
         * Sets if we are in keytip mode.
         * Note, this sets both the state for the layer as well as
         * the value that the manager will expose externally.
         * @param inKeytipMode - Boolean so set whether we are in keytip mode or not
         */
        _this._setInKeytipMode = function (inKeytipMode) {
            _this.setState({ inKeytipMode: inKeytipMode });
            _this._keytipManager.inKeytipMode = inKeytipMode;
        };
        var managerKeytips = _this._keytipManager.getKeytips().slice();
        _this.state = {
            inKeytipMode: false,
            // Get the initial set of keytips
            keytips: managerKeytips,
            visibleKeytips: _this._getVisibleKeytips(managerKeytips)
        };
        _this._keytipTree = new KeytipTree();
        // Add regular and persisted keytips to the tree
        for (var _i = 0, _a = _this._keytipManager.keytips.concat(_this._keytipManager.persistedKeytips); _i < _a.length; _i++) {
            var uniqueKeytip = _a[_i];
            _this._keytipTree.addNode(uniqueKeytip.keytip, uniqueKeytip.uniqueID);
        }
        _this._currentSequence = '';
        // Add keytip listeners
        _this._events.on(_this._keytipManager, KeytipEvents.KEYTIP_ADDED, _this._onKeytipAdded);
        _this._events.on(_this._keytipManager, KeytipEvents.KEYTIP_UPDATED, _this._onKeytipUpdated);
        _this._events.on(_this._keytipManager, KeytipEvents.KEYTIP_REMOVED, _this._onKeytipRemoved);
        _this._events.on(_this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_ADDED, _this._onPersistedKeytipAdded);
        _this._events.on(_this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_REMOVED, _this._onPersistedKeytipRemoved);
        _this._events.on(_this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_EXECUTE, _this._onPersistedKeytipExecute);
        return _this;
    }
    KeytipLayerBase.prototype.render = function () {
        var _this = this;
        var _a = this.props, content = _a.content, styles = _a.styles;
        var _b = this.state, keytips = _b.keytips, visibleKeytips = _b.visibleKeytips;
        this._classNames = getClassNames(styles);
        return (React.createElement(Layer, { styles: getLayerStyles },
            React.createElement("span", { id: KTP_LAYER_ID, className: this._classNames.innerContent }, "" + content + KTP_ARIA_SEPARATOR),
            keytips &&
                keytips.map(function (keytipProps, index) {
                    return (React.createElement("span", { key: index, id: sequencesToID(keytipProps.keySequences), className: _this._classNames.innerContent }, keytipProps.keySequences.join(KTP_ARIA_SEPARATOR)));
                }),
            visibleKeytips &&
                visibleKeytips.map(function (visibleKeytipProps) {
                    return React.createElement(Keytip, tslib_1.__assign({ key: sequencesToID(visibleKeytipProps.keySequences) }, visibleKeytipProps));
                })));
    };
    KeytipLayerBase.prototype.componentDidMount = function () {
        // Add window listeners
        this._events.on(window, 'mouseup', this._onDismiss, true /* useCapture */);
        this._events.on(window, 'pointerup', this._onDismiss, true /* useCapture */);
        this._events.on(window, 'resize', this._onDismiss);
        this._events.on(window, 'keydown', this._onKeyDown, true /* useCapture */);
        this._events.on(window, 'keypress', this._onKeyPress, true /* useCapture */);
        this._events.on(window, 'scroll', this._onDismiss, true /* useCapture */);
        // Add keytip listeners
        this._events.on(this._keytipManager, KeytipEvents.ENTER_KEYTIP_MODE, this._enterKeytipMode);
        this._events.on(this._keytipManager, KeytipEvents.EXIT_KEYTIP_MODE, this._exitKeytipMode);
    };
    KeytipLayerBase.prototype.componentWillUnmount = function () {
        // Remove window listeners
        this._events.off(window, 'mouseup', this._onDismiss, true /* useCapture */);
        this._events.off(window, 'pointerup', this._onDismiss, true /* useCapture */);
        this._events.off(window, 'resize', this._onDismiss);
        this._events.off(window, 'keydown', this._onKeyDown, true /* useCapture */);
        this._events.off(window, 'keypress', this._onKeyPress, true /* useCapture */);
        this._events.off(window, 'scroll', this._onDismiss, true /* useCapture */);
        // Remove keytip listeners
        this._events.off(this._keytipManager, KeytipEvents.KEYTIP_ADDED, this._onKeytipAdded);
        this._events.off(this._keytipManager, KeytipEvents.KEYTIP_UPDATED, this._onKeytipUpdated);
        this._events.off(this._keytipManager, KeytipEvents.KEYTIP_REMOVED, this._onKeytipRemoved);
        this._events.off(this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_ADDED, this._onPersistedKeytipAdded);
        this._events.off(this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_REMOVED, this._onPersistedKeytipRemoved);
        this._events.off(this._keytipManager, KeytipEvents.PERSISTED_KEYTIP_EXECUTE, this._onPersistedKeytipExecute);
        this._events.off(this._keytipManager, KeytipEvents.ENTER_KEYTIP_MODE, this._enterKeytipMode);
        this._events.off(this._keytipManager, KeytipEvents.EXIT_KEYTIP_MODE, this._exitKeytipMode);
    };
    // The below public functions are only public for testing purposes
    // They are not intended to be used in app code by using a KeytipLayer reference
    KeytipLayerBase.prototype.getCurrentSequence = function () {
        return this._currentSequence;
    };
    KeytipLayerBase.prototype.getKeytipTree = function () {
        return this._keytipTree;
    };
    /**
     * Processes an IKeytipTransitionKey entered by the user
     *
     * @param transitionKey - IKeytipTransitionKey received by the layer to process
     */
    KeytipLayerBase.prototype.processTransitionInput = function (transitionKey, ev) {
        var currKtp = this._keytipTree.currentKeytip;
        if (transitionKeysContain(this.props.keytipExitSequences, transitionKey) && currKtp) {
            // If key sequence is in 'exit sequences', exit keytip mode
            this._keyHandled = true;
            this._exitKeytipMode(ev);
        }
        else if (transitionKeysContain(this.props.keytipReturnSequences, transitionKey)) {
            // If key sequence is in return sequences, move currentKeytip to parent (or if currentKeytip is the root, exit)
            if (currKtp) {
                this._keyHandled = true;
                if (currKtp.id === this._keytipTree.root.id) {
                    // We are at the root, exit keytip mode
                    this._exitKeytipMode(ev);
                }
                else {
                    // If this keytip has a onReturn prop, we execute the func.
                    if (currKtp.onReturn) {
                        currKtp.onReturn(this._getKtpExecuteTarget(currKtp), this._getKtpTarget(currKtp));
                    }
                    // Reset currentSequence
                    this._currentSequence = '';
                    // Return pointer to its parent
                    this._keytipTree.currentKeytip = this._keytipTree.getNode(currKtp.parent);
                    // Show children keytips of the new currentKeytip
                    this.showKeytips(this._keytipTree.getChildren());
                }
            }
        }
        else if (transitionKeysContain(this.props.keytipStartSequences, transitionKey) && !currKtp) {
            // If key sequence is in 'entry sequences' and currentKeytip is null, we enter keytip mode
            this._keyHandled = true;
            this._enterKeytipMode();
        }
    };
    /**
     * Processes inputs from the document listener and traverse the keytip tree
     *
     * @param key - Key pressed by the user
     */
    KeytipLayerBase.prototype.processInput = function (key, ev) {
        // Concat the input key with the current sequence
        var currSequence = this._currentSequence + key;
        var currKtp = this._keytipTree.currentKeytip;
        // currentKeytip must be defined, otherwise we haven't entered keytip mode yet
        if (currKtp) {
            var node = this._keytipTree.getExactMatchedNode(currSequence, currKtp);
            if (node) {
                this._keytipTree.currentKeytip = currKtp = node;
                var currKtpChildren = this._keytipTree.getChildren();
                // Execute this node's onExecute if defined
                if (currKtp.onExecute) {
                    currKtp.onExecute(this._getKtpExecuteTarget(currKtp), this._getKtpTarget(currKtp));
                    // Reset currKtp, this might have changed from the onExecute
                    currKtp = this._keytipTree.currentKeytip;
                }
                // To exit keytipMode after executing the keytip it must not have a menu or have dynamic children
                if (currKtpChildren.length === 0 && !(currKtp.hasDynamicChildren || currKtp.hasMenu)) {
                    this._exitKeytipMode(ev);
                }
                else {
                    // Show all children keytips
                    this.showKeytips(currKtpChildren);
                }
                // Clear currentSequence
                this._currentSequence = '';
                return;
            }
            var partialNodes = this._keytipTree.getPartiallyMatchedNodes(currSequence, currKtp);
            if (partialNodes.length > 0) {
                // We found nodes that partially match the sequence, so we show only those
                // Omit showing persisted nodes here
                var ids = partialNodes
                    .filter(function (partialNode) {
                    return !partialNode.persisted;
                })
                    .map(function (partialNode) {
                    return partialNode.id;
                });
                this.showKeytips(ids);
                // Save currentSequence
                this._currentSequence = currSequence;
            }
        }
    };
    /**
     * Show the given keytips and hide all others
     *
     * @param ids - Keytip IDs to show
     */
    KeytipLayerBase.prototype.showKeytips = function (ids) {
        // Update the visible prop in the manager
        for (var _i = 0, _a = this._keytipManager.getKeytips(); _i < _a.length; _i++) {
            var keytip = _a[_i];
            var keytipId = sequencesToID(keytip.keySequences);
            if (ids.indexOf(keytipId) >= 0) {
                keytip.visible = true;
            }
            else if (keytip.overflowSetSequence &&
                ids.indexOf(sequencesToID(mergeOverflows(keytip.keySequences, keytip.overflowSetSequence))) >= 0) {
                // Check if the ID with the overflow is the keytip we're looking for
                keytip.visible = true;
            }
            else {
                keytip.visible = false;
            }
        }
        // Apply the manager changes to the Layer state
        this._setKeytips();
    };
    /**
     * Enters keytip mode for this layer
     */
    KeytipLayerBase.prototype._enterKeytipMode = function () {
        if (this._keytipManager.shouldEnterKeytipMode) {
            this._keytipTree.currentKeytip = this._keytipTree.root;
            // Show children of root
            this.showKeytips(this._keytipTree.getChildren());
            this._setInKeytipMode(true /* inKeytipMode */);
            if (this.props.onEnterKeytipMode) {
                this.props.onEnterKeytipMode();
            }
        }
    };
    /**
     * Exits keytip mode for this layer
     */
    KeytipLayerBase.prototype._exitKeytipMode = function (ev) {
        this._keytipTree.currentKeytip = undefined;
        this._currentSequence = '';
        // Hide all keytips
        this.showKeytips([]);
        // Reset the delayed keytips if any
        this._delayedQueueTimeout && this._async.clearTimeout(this._delayedQueueTimeout);
        this._delayedKeytipQueue = [];
        this._setInKeytipMode(false /* inKeytipMode */);
        if (this.props.onExitKeytipMode) {
            this.props.onExitKeytipMode(ev);
        }
    };
    /**
     * Sets the keytips state property
     *
     * @param keytipProps - Keytips to set in this layer
     */
    KeytipLayerBase.prototype._setKeytips = function (keytipProps) {
        if (keytipProps === void 0) { keytipProps = this._keytipManager.getKeytips(); }
        this.setState({ keytips: keytipProps, visibleKeytips: this._getVisibleKeytips(keytipProps) });
    };
    /**
     * Callback function to use for persisted keytips
     *
     * @param overflowButtonSequences - The overflow button sequence to execute
     * @param keytipSequences - The keytip that should become the 'currentKeytip' when it is registered
     */
    KeytipLayerBase.prototype._persistedKeytipExecute = function (overflowButtonSequences, keytipSequences) {
        // Save newCurrentKeytip for later
        this._newCurrentKeytipSequences = keytipSequences;
        // Execute the overflow button's onExecute
        var overflowKeytipNode = this._keytipTree.getNode(sequencesToID(overflowButtonSequences));
        if (overflowKeytipNode && overflowKeytipNode.onExecute) {
            overflowKeytipNode.onExecute(this._getKtpExecuteTarget(overflowKeytipNode), this._getKtpTarget(overflowKeytipNode));
        }
    };
    KeytipLayerBase.prototype._getVisibleKeytips = function (keytips) {
        return keytips.filter(function (keytip) {
            return keytip.visible;
        });
    };
    /**
     * Gets the ModifierKeyCodes based on the keyboard event
     *
     * @param ev - React.KeyboardEvent
     * @returns List of ModifierKeyCodes that were pressed
     */
    KeytipLayerBase.prototype._getModifierKey = function (key, ev) {
        var modifierKeys = [];
        if (ev.altKey && key !== 'Alt') {
            modifierKeys.push(KeytipTransitionModifier.alt);
        }
        if (ev.ctrlKey && key !== 'Control') {
            modifierKeys.push(KeytipTransitionModifier.ctrl);
        }
        if (ev.shiftKey && key !== 'Shift') {
            modifierKeys.push(KeytipTransitionModifier.shift);
        }
        if (ev.metaKey && key !== 'Meta') {
            modifierKeys.push(KeytipTransitionModifier.meta);
        }
        return modifierKeys.length ? modifierKeys : undefined;
    };
    /**
     * Trigger a keytip immediately and set it as the current keytip
     *
     * @param keytipProps - Keytip to trigger immediately
     */
    KeytipLayerBase.prototype._triggerKeytipImmediately = function (keytipProps) {
        // This keytip should become the currentKeytip and should execute right away
        var keytipSequence = keytipProps.keySequences.slice();
        if (keytipProps.overflowSetSequence) {
            keytipSequence = mergeOverflows(keytipSequence, keytipProps.overflowSetSequence);
        }
        // Set currentKeytip
        this._keytipTree.currentKeytip = this._keytipTree.getNode(sequencesToID(keytipSequence));
        if (this._keytipTree.currentKeytip) {
            // Show all children keytips if any
            var children = this._keytipTree.getChildren();
            if (children.length) {
                this.showKeytips(children);
            }
            if (this._keytipTree.currentKeytip.onExecute) {
                this._keytipTree.currentKeytip.onExecute(this._getKtpExecuteTarget(this._keytipTree.currentKeytip), this._getKtpTarget(this._keytipTree.currentKeytip));
            }
        }
        // Unset _newCurrKtpSequences
        this._newCurrentKeytipSequences = undefined;
    };
    KeytipLayerBase.prototype._addKeytipToQueue = function (keytipID) {
        var _this = this;
        // Add keytip
        this._delayedKeytipQueue.push(keytipID);
        // Clear timeout
        this._delayedQueueTimeout && this._async.clearTimeout(this._delayedQueueTimeout);
        // Reset timeout
        this._delayedQueueTimeout = this._async.setTimeout(function () {
            if (_this._delayedKeytipQueue.length) {
                _this.showKeytips(_this._delayedKeytipQueue);
                _this._delayedKeytipQueue = [];
            }
        }, 300);
    };
    KeytipLayerBase.prototype._removeKeytipFromQueue = function (keytipID) {
        var _this = this;
        var index = this._delayedKeytipQueue.indexOf(keytipID);
        if (index >= 0) {
            // Remove keytip
            this._delayedKeytipQueue.splice(index, 1);
            // Clear timeout
            this._delayedQueueTimeout && this._async.clearTimeout(this._delayedQueueTimeout);
            // Reset timeout
            this._delayedQueueTimeout = this._async.setTimeout(function () {
                if (_this._delayedKeytipQueue.length) {
                    _this.showKeytips(_this._delayedKeytipQueue);
                    _this._delayedKeytipQueue = [];
                }
            }, 300);
        }
    };
    KeytipLayerBase.prototype._getKtpExecuteTarget = function (currKtp) {
        return getDocument().querySelector(ktpTargetFromId(currKtp.id));
    };
    KeytipLayerBase.prototype._getKtpTarget = function (currKtp) {
        return getDocument().querySelector(ktpTargetFromSequences(currKtp.keySequences));
    };
    /**
     * Returns T/F if the keytipProps keySequences match the currentKeytip, and the currentKeytip is in an overflow well
     * This will make 'keytipProps' the new currentKeytip
     *
     * @param keytipProps - Keytip props to check
     * @returns {boolean} - T/F if this keytip should become the currentKeytip
     */
    KeytipLayerBase.prototype._isCurrentKeytipAnAlias = function (keytipProps) {
        var currKtp = this._keytipTree.currentKeytip;
        if (currKtp &&
            (currKtp.overflowSetSequence || currKtp.persisted) &&
            arraysEqual(keytipProps.keySequences, currKtp.keySequences)) {
            return true;
        }
        return false;
    };
    KeytipLayerBase.defaultProps = {
        keytipStartSequences: [defaultStartSequence],
        keytipExitSequences: [defaultExitSequence],
        keytipReturnSequences: [defaultReturnSequence],
        content: ''
    };
    return KeytipLayerBase;
}(BaseComponent));
export { KeytipLayerBase };
//# sourceMappingURL=KeytipLayer.base.js.map