"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
var React = require("react");
var ClassNames_1 = require("../util/ClassNames");
var TextAreaCaretPosition_1 = require("../util/TextAreaCaretPosition");
var InsertTextAtPosition_1 = require("../util/InsertTextAtPosition");
var Math_1 = require("../util/Math");
var SuggestionsDropdown_1 = require("./SuggestionsDropdown");
var TextArea = /** @class */ (function (_super) {
    __extends(TextArea, _super);
    function TextArea(props) {
        var _this = _super.call(this, props) || this;
        _this.currentLoadSuggestionsPromise = Promise.resolve(undefined);
        /**
         * suggestionsPromiseIndex exists as a means to cancel what happens when the suggestions promise finishes loading.
         *
         * When the user is searching for suggestions, there is a promise that, when resolved, causes a re-render.
         * However, in case there is another promise to be resolved after the current one, it does not make sense to re-render
         * only to re-render again after the next one is complete.
         *
         * When there is a promise loading and the user cancels the suggestion, you don't want the status to go back to "active"
         * when the promise resolves.
         *
         * suggestionsPromiseIndex increments every time the mentions query
         */
        _this.suggestionsPromiseIndex = 0;
        _this.getTextArea = function () {
            return _this.props.refObject.current;
        };
        _this.handleOnChange = function (event) {
            var onChange = _this.props.onChange;
            onChange(event.target.value);
        };
        _this.handleBlur = function () {
            var mention = _this.state.mention;
            if (mention) {
                _this.setState({ mention: { status: "inactive", suggestions: [] } });
            }
        };
        _this.startLoadingSuggestions = function (text) {
            var promiseIndex = ++_this.suggestionsPromiseIndex;
            var loadSuggestions = _this.props.loadSuggestions;
            _this.currentLoadSuggestionsPromise = _this.currentLoadSuggestionsPromise
                .then(function () { return loadSuggestions(text, _this.state.mention.triggeredBy); })
                .then(function (suggestions) {
                if (_this.state.mention.status === "inactive") {
                    // This means this promise resolved too late when the status has already been set to inactice
                    return;
                }
                else if (_this.suggestionsPromiseIndex === promiseIndex) {
                    if (!suggestions || !suggestions.length) {
                        _this.setState({
                            mention: {
                                status: "inactive",
                                suggestions: []
                            }
                        });
                    }
                    else {
                        _this.setState({
                            mention: __assign(__assign({}, _this.state.mention), { status: "active", suggestions: suggestions, focusIndex: 0 })
                        });
                    }
                    _this.suggestionsPromiseIndex = 0;
                }
                return Promise.resolve();
            });
        };
        _this.loadEmptySuggestion = function (target, key) {
            var caret = TextAreaCaretPosition_1.getCaretCoordinates(target, key);
            _this.startLoadingSuggestions("");
            _this.setState({
                mention: {
                    status: "loading",
                    startPosition: target.selectionStart + 1,
                    caret: caret,
                    suggestions: [],
                    triggeredBy: key
                }
            });
        };
        _this.handleSuggestionSelected = function (index) {
            var mention = _this.state.mention;
            _this.getTextArea().selectionStart = mention.startPosition - 1;
            var textForInsert = _this.props.value.substr(_this.getTextArea().selectionStart, _this.getTextArea().selectionEnd - _this.getTextArea().selectionStart);
            InsertTextAtPosition_1.insertText(_this.getTextArea(), mention.suggestions[index].value + " ");
            _this.setState({
                mention: {
                    status: "inactive",
                    suggestions: []
                }
            });
        };
        _this.handleKeyDown = function (event) {
            if (_this.props.onPossibleKeyCommand) {
                var handled = _this.props.onPossibleKeyCommand(event);
                if (handled) {
                    event.preventDefault();
                    // If the keydown resulted in a command being executed, we will just close the suggestions if they are open.
                    // Resetting suggestionsPromiseIndex will cause any promise that is yet to be resolved to have no effect
                    // when they finish loading.
                    // TODO: The code below is duplicate, we need to clean this up
                    _this.suggestionsPromiseIndex = 0;
                    _this.setState({
                        mention: {
                            status: "inactive",
                            suggestions: []
                        }
                    });
                    return;
                }
            }
            if (!_this.suggestionsEnabled()) {
                return;
            }
            var key = event.key, shiftKey = event.shiftKey, currentTarget = event.currentTarget;
            var selectionStart = currentTarget.selectionStart;
            var mention = _this.state.mention;
            switch (mention.status) {
                case "loading":
                case "active":
                    if (key === "Escape" ||
                        (key === "Backspace" &&
                            selectionStart <= _this.state.mention.startPosition)) {
                        // resetting suggestionsPromiseIndex will cause any promise that is yet to be resolved to have no effect
                        // when they finish loading.
                        _this.suggestionsPromiseIndex = 0;
                        _this.setState({
                            mention: {
                                status: "inactive",
                                suggestions: []
                            }
                        });
                    }
                    else if (mention.status === "active" &&
                        (key === "ArrowUp" || key === "ArrowDown") &&
                        !shiftKey) {
                        event.preventDefault();
                        var focusDelta = key === "ArrowUp" ? -1 : 1;
                        _this.setState({
                            mention: __assign(__assign({}, mention), { focusIndex: Math_1.mod(mention.focusIndex + focusDelta, mention.suggestions.length) })
                        });
                    }
                    else if (key === "Enter" &&
                        mention.status === "active" &&
                        mention.suggestions.length) {
                        event.preventDefault();
                        _this.handleSuggestionSelected(mention.focusIndex);
                    }
                    break;
                default:
                // Ignore
            }
        };
        _this.handleKeyUp = function (event) {
            var key = event.key;
            var mention = _this.state.mention;
            var _a = _this.props, suggestionTriggerCharacters = _a.suggestionTriggerCharacters, value = _a.value;
            switch (mention.status) {
                case "loading":
                case "active":
                    if (key === "Backspace") {
                        var searchText = value.substr(mention.startPosition, _this.getTextArea().selectionStart - mention.startPosition);
                        _this.startLoadingSuggestions(searchText);
                        if (mention.status !== "loading") {
                            _this.setState({
                                mention: __assign(__assign({}, _this.state.mention), { status: "loading" })
                            });
                        }
                    }
                    break;
                case "inactive":
                    if (key === "Backspace") {
                        var prevChar = value.charAt(_this.getTextArea().selectionStart - 1);
                        var isAtMention = suggestionTriggerCharacters.includes(value.charAt(_this.getTextArea().selectionStart - 1));
                        if (isAtMention) {
                            _this.loadEmptySuggestion(event.currentTarget, prevChar);
                        }
                    }
                    break;
                default:
                // Ignore
            }
        };
        _this.handleKeyPress = function (event) {
            var _a = _this.props, suggestionTriggerCharacters = _a.suggestionTriggerCharacters, value = _a.value;
            var mention = _this.state.mention;
            var key = event.key;
            switch (mention.status) {
                case "loading":
                case "active":
                    if (key === " ") {
                        _this.setState({
                            mention: __assign(__assign({}, _this.state.mention), { status: "inactive" })
                        });
                        return;
                    }
                    var searchText = value.substr(mention.startPosition, _this.getTextArea().selectionStart - mention.startPosition) + key;
                    // In this case, the mentions box was open but the user typed something else
                    _this.startLoadingSuggestions(searchText);
                    if (mention.status !== "loading") {
                        _this.setState({
                            mention: __assign(__assign({}, _this.state.mention), { status: "loading" })
                        });
                    }
                    break;
                case "inactive":
                    if (suggestionTriggerCharacters.indexOf(event.key) === -1 ||
                        !/\s|\(|\[|^.{0}$/.test(value.charAt(_this.getTextArea().selectionStart - 1))) {
                        return;
                    }
                    _this.loadEmptySuggestion(event.currentTarget, event.key);
                    break;
            }
        };
        _this.state = { mention: { status: "inactive", suggestions: [] } };
        return _this;
    }
    TextArea.prototype.suggestionsEnabled = function () {
        return (this.props.suggestionTriggerCharacters &&
            this.props.suggestionTriggerCharacters.length &&
            this.props.loadSuggestions);
    };
    TextArea.prototype.render = function () {
        var _this = this;
        var _a = this.props, classes = _a.classes, readOnly = _a.readOnly, textAreaProps = _a.textAreaProps, height = _a.height, value = _a.value, suggestionTriggerCharacters = _a.suggestionTriggerCharacters, loadSuggestions = _a.loadSuggestions, suggestionsDropdownClasses = _a.suggestionsDropdownClasses, textAreaComponent = _a.textAreaComponent, onPaste = _a.onPaste;
        var suggestionsEnabled = suggestionTriggerCharacters &&
            suggestionTriggerCharacters.length &&
            loadSuggestions;
        var mention = this.state.mention;
        var TextAreaComponent = (textAreaComponent ||
            "textarea");
        return (React.createElement("div", { className: "mde-textarea-wrapper" },
            React.createElement(TextAreaComponent, __assign({ className: ClassNames_1.classNames("mde-text", classes), style: { height: height }, ref: this.props.refObject, readOnly: readOnly, value: value, "data-testid": "text-area" }, textAreaProps, { onChange: function (event) {
                    var _a, _b, _c;
                    (_c = (_a = textAreaProps) === null || _a === void 0 ? void 0 : (_b = _a).onChange) === null || _c === void 0 ? void 0 : _c.call(_b, event);
                    _this.handleOnChange(event);
                }, onBlur: function (event) {
                    var _a, _b, _c;
                    if (suggestionsEnabled) {
                        (_c = (_a = textAreaProps) === null || _a === void 0 ? void 0 : (_b = _a).onBlur) === null || _c === void 0 ? void 0 : _c.call(_b, event);
                        _this.handleBlur();
                    }
                }, onKeyDown: function (event) {
                    var _a, _b, _c;
                    (_c = (_a = textAreaProps) === null || _a === void 0 ? void 0 : (_b = _a).onKeyDown) === null || _c === void 0 ? void 0 : _c.call(_b, event);
                    _this.handleKeyDown(event);
                }, onKeyUp: function (event) {
                    var _a, _b, _c;
                    if (suggestionsEnabled) {
                        (_c = (_a = textAreaProps) === null || _a === void 0 ? void 0 : (_b = _a).onKeyUp) === null || _c === void 0 ? void 0 : _c.call(_b, event);
                        _this.handleKeyUp(event);
                    }
                }, onKeyPress: function (event) {
                    var _a, _b, _c;
                    if (suggestionsEnabled) {
                        (_c = (_a = textAreaProps) === null || _a === void 0 ? void 0 : (_b = _a).onKeyPress) === null || _c === void 0 ? void 0 : _c.call(_b, event);
                        _this.handleKeyPress(event);
                    }
                }, onPaste: function (event) {
                    var _a, _b, _c;
                    (_c = (_a = textAreaProps) === null || _a === void 0 ? void 0 : (_b = _a).onPaste) === null || _c === void 0 ? void 0 : _c.call(_b, event);
                    onPaste(event);
                } })),
            mention.status === "active" && mention.suggestions.length && (React.createElement(SuggestionsDropdown_1.SuggestionsDropdown, { classes: suggestionsDropdownClasses, caret: mention.caret, suggestions: mention.suggestions, onSuggestionSelected: this.handleSuggestionSelected, focusIndex: mention.focusIndex, textAreaRef: this.props.refObject }))));
    };
    return TextArea;
}(React.Component));
exports.TextArea = TextArea;
