import * as React from 'react';
import { concat, merge, Subject, Subscription } from 'rxjs';
import { catchError, filter, map, startWith, switchMap } from 'rxjs/operators';
import getCaretCoordinates from 'textarea-caret';
import { fetchAllUsers } from '../../../site-admin/backend';
import { eventLogger } from '../../../tracking/eventLogger';
class UserNode extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.onMouseDown = e => {
            e.preventDefault();
            this.props.onClick();
        };
    }
    render() {
        return (React.createElement("a", { className: `discussions-input-mention-overlay__list-group-item list-group-item list-group-item-action ${this.props.selected ? ' active' : ''}`, href: "#", onMouseDown: this.onMouseDown },
            React.createElement("div", { className: "d-flex align-items-center justify-content-between" },
                React.createElement("div", null,
                    React.createElement("strong", null, this.props.node.username),
                    this.props.node.displayName && (React.createElement("span", { className: "discussions-input-mention-overlay__text-muted ml-2 text-muted" }, this.props.node.displayName))))));
    }
}
/**
 * Represents a selection in a list of items.
 */
class ListSelectionState {
    /**
     * Creates a new ListSelectionState.
     * @param index The item in the list that is currently selected. Out of bounds selections wrap around.
     * @param length The length of the list.
     */
    constructor(index, length) {
        if (index < 0) {
            this.index = length - 1; // wrap around
        }
        else if (index > length - 1) {
            this.index = 0; // wrap around
        }
        else {
            this.index = index;
        }
        this.length = length;
    }
    /** Returns a new ListSelectionState representing the user's wishes to move down in the list by one item. */
    static down(a) {
        return new ListSelectionState(a.index + 1, a.length);
    }
    /** Returns a new ListSelectionState representing the user's wishes to move up in the list by one item. */
    static up(a) {
        return new ListSelectionState(a.index - 1, a.length);
    }
}
export class DiscussionsInputMentionOverlay extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.componentUpdates = new Subject();
        this.subscriptions = new Subscription();
        /** Fired when the user to search for has changed. */
        this.userSearches = new Subject();
        this.state = {
            position: { top: 0, left: 0 },
            loading: false,
            selectionState: new ListSelectionState(0, 0),
            lastTextAreaValue: '',
        };
        this.select = (index) => {
            // Value up to and including the '@'
            const preText = this.props.textAreaValue.slice(0, this.props.selectionStart - this.state.userSearch.length);
            // Value after the '@mention'
            const postText = this.props.textAreaValue.slice(this.props.selectionStart);
            const newMention = this.state.connection.nodes[index].username;
            const newValue = preText + newMention + (postText.startsWith(' ') ? '' : ' ');
            this.setState({
                userSearch: undefined,
                lastTextAreaValue: newValue + postText,
            }, () => this.props.setTextAreaValue({
                newValue: newValue + postText,
                newSelectionStart: newValue.length,
            }));
        };
        this.dismiss = () => {
            this.setState({ userSearch: undefined });
        };
        this.onKeyDownFilter = (e) => {
            if (!this.state.userSearch) {
                return false;
            }
            if (e.key === 'ArrowDown') {
                e.preventDefault();
                this.setState((state) => ({ selectionState: ListSelectionState.down(state.selectionState) }));
            }
            else if (e.key === 'ArrowUp') {
                e.preventDefault();
                this.setState((state) => ({ selectionState: ListSelectionState.up(state.selectionState) }));
            }
            else if (e.key === 'Enter' || e.key === 'Tab') {
                e.preventDefault();
                this.select(this.state.selectionState.index);
            }
            else if (e.key === 'Escape') {
                e.preventDefault();
                this.dismiss();
            }
            return true;
        };
    }
    componentDidMount() {
        this.props.setOnBlurHandler(this.dismiss);
        this.props.setOnKeyDownFilter(this.onKeyDownFilter);
        this.subscriptions.add(merge(this.componentUpdates.pipe(startWith(this.props)).pipe(filter(props => props.textAreaValue !== this.state.lastTextAreaValue), switchMap((props) => {
            const { textAreaValue, selectionStart } = props;
            // Check the text that the user is currently typing to see if
            // they have typed "@". We support:
            //
            // - Typing @user at the end of the text area input.
            // - Typing @user in the middle of the text area input (i.e. with
            //   text to the right of the cursor).
            // - Typing an email address without the overlay showing / getting
            //   in the way ("@" must be preceded by whitespace).
            // - The username is considered anything that comes after the "@"
            //   except whitespace, period, or comma.
            //
            const textToCursor = textAreaValue.slice(0, selectionStart);
            const [, , mention] = textToCursor.match(/(^|\s)@([^\s\.\,]*)$/) || [null, null, null];
            if (!mention) {
                this.userSearches.next(null);
                return [{ userSearch: undefined, lastTextAreaValue: textAreaValue }];
            }
            this.userSearches.next(mention);
            const caret = getCaretCoordinates(props.textAreaElement, props.selectionStart - '@'.length - mention.length);
            eventLogger.log('DiscussionsMentionOverlayShown');
            return [
                {
                    userSearch: mention,
                    lastTextAreaValue: textAreaValue,
                    position: {
                        top: caret.top + caret.height,
                        left: caret.left,
                    },
                },
            ];
        })), 
        // Handle user searches by fetching the new list of users from the server and updating the state.
        this.userSearches.pipe(switchMap((userSearch) => {
            if (!userSearch) {
                return [{ loading: false, error: undefined, connection: undefined }];
            }
            return concat([{ loading: true, error: undefined }], fetchAllUsers({ first: 100, query: userSearch }).pipe(map(connection => ({
                loading: false,
                error: undefined,
                connection,
                selectionState: new ListSelectionState(0, connection.nodes.length),
            })), catchError(error => {
                console.error(error);
                return [{ loading: false, error, connection: undefined }];
            })));
        }))).subscribe(stateUpdate => this.setState((state) => (Object.assign({}, state, stateUpdate))), err => console.error(err)));
        this.componentUpdates.next(this.props);
    }
    componentDidUpdate() {
        this.componentUpdates.next(this.props);
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    render() {
        const { userSearch, position, loading, error, connection, selectionState } = this.state;
        if (!userSearch) {
            return null;
        }
        return (React.createElement("div", { 
            // tslint:disable-next-line:jsx-ban-props needed for dynamic styling
            style: {
                position: 'relative',
                height: 0,
                left: position.left,
                top: position.top,
            } },
            React.createElement("ul", { className: "discussions-input-mention-overlay__list-group list-group" },
                loading &&
                    !connection && (React.createElement("li", { className: "discussions-input-mention-overlay__list-group-item list-group-item" }, "Loading...")),
                error && (React.createElement("li", { className: "discussions-input-mention-overlay__list-group-item list-group-item" },
                    "Error: ",
                    error)),
                connection &&
                    connection.nodes.map((node, index) => (React.createElement(UserNode, { key: node.id, selected: index === selectionState.index, node: node, 
                        // tslint:disable-next-line:jsx-no-lambda
                        onClick: () => this.select(index) }))),
                connection &&
                    connection.nodes.length === 0 && (React.createElement("li", { className: "discussions-input-mention-overlay__list-group-item list-group-item" }, "no results")))));
    }
}
//# sourceMappingURL=DiscussionsInputMentionOverlay.js.map