import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import AlertCircleIcon from 'mdi-react/AlertCircleIcon';
import * as React from 'react';
import { merge, of, Subject, Subscription } from 'rxjs';
import { catchError, concat, filter, map, mergeMap, startWith, tap, withLatestFrom } from 'rxjs/operators';
import { Form } from '../../../components/Form';
import { Markdown } from '../../../components/Markdown';
import { Spacer, TabBorderClassName, TabsWithLocalStorageViewStatePersistence } from '../../../components/Tabs';
import { renderMarkdown } from '../../../discussions/backend';
import { eventLogger } from '../../../tracking/eventLogger';
import { asError } from '../../../util/errors';
import { DiscussionsInputMentionOverlay } from './DiscussionsInputMentionOverlay';
/**
 * How & whether or not to render a title input field.
 */
export var TitleMode;
(function (TitleMode) {
    /** Explicitly show a separate title input field. */
    TitleMode[TitleMode["Explicit"] = 0] = "Explicit";
    /** Implicitly use the first line of the main textarea as the title field (like Git commit messages). */
    TitleMode[TitleMode["Implicit"] = 1] = "Implicit";
    /** No title input at all, e.g. for replying to discussion threads.  */
    TitleMode[TitleMode["None"] = 2] = "None";
})(TitleMode || (TitleMode = {}));
export class DiscussionsInput extends React.PureComponent {
    constructor() {
        super(...arguments);
        this.componentUpdates = new Subject();
        this.subscriptions = new Subscription();
        this.submits = new Subject();
        this.nextSubmit = (e) => this.submits.next(e);
        this.titleInputChanges = new Subject();
        this.nextTitleInputChange = (e) => this.titleInputChanges.next(e.currentTarget.value);
        this.textAreaKeyDowns = new Subject();
        this.nextTextAreaKeyDown = (e) => {
            if (this.onKeyDownFilter && this.onKeyDownFilter(e)) {
                return;
            }
            this.textAreaKeyDowns.next(e);
        };
        this.textAreaChanges = new Subject();
        this.nextTextAreaChange = (e) => {
            this.textAreaChanges.next({
                textAreaValue: e.currentTarget.value,
                selectionStart: e.currentTarget.selectionStart,
                element: e.currentTarget,
            });
        };
        this.tabChanges = new Subject();
        this.nextTabChange = (tab) => this.tabChanges.next(tab);
        this.state = {
            titleInputValue: '',
            textArea: { textAreaValue: '', selectionStart: 0 },
            submitting: false,
        };
        /** Trims the implicit title string out of the comment (e.g. textarea value). */
        this.trimImplicitTitle = (comment) => {
            if (this.props.titleMode !== TitleMode.Implicit) {
                return comment;
            }
            return comment
                .trimLeft()
                .split('\n')
                .slice(1)
                .join('\n');
        };
        this.canSubmit = () => {
            const textAreaEmpty = !this.state.textArea.textAreaValue.trim();
            const titleRequired = this.props.titleMode !== TitleMode.None;
            const titleEmpty = !this.state.titleInputValue.trim();
            return !this.state.submitting && !textAreaEmpty && (!titleRequired || !titleEmpty);
        };
        this.setOnBlurHandler = (h) => {
            this.onBlurHandler = h;
        };
        this.setOnKeyDownFilter = (f) => {
            this.onKeyDownFilter = f;
        };
        this.setTextAreaValue = (v) => {
            this.textAreaChanges.next({
                textAreaValue: v.newValue,
                selectionStart: v.newSelectionStart,
                element: this.state.textArea.element,
            });
            this.textAreaRef.value = v.newValue;
            this.textAreaRef.selectionStart = v.newSelectionStart;
            this.textAreaRef.selectionEnd = v.newSelectionStart;
        };
        this.setTextAreaRef = (ref) => (this.textAreaRef = ref);
    }
    // TODO(slimsag:discussions): ASAP: "preview" tab does not get reset after you submit a comment
    componentDidMount() {
        this.subscriptions.add(merge(this.titleInputChanges.pipe(tap(titleInputValue => this.props.onTitleChange && this.props.onTitleChange(titleInputValue)), map((titleInputValue) => state => (Object.assign({}, state, { titleInputValue })))), this.textAreaChanges.pipe(startWith({ textAreaValue: '', selectionStart: 0, element: undefined }), map((textArea) => state => {
            if (this.props.titleMode === TitleMode.Implicit) {
                this.titleInputChanges.next(textArea.textAreaValue.trimLeft().split('\n')[0]);
            }
            return Object.assign({}, state, { textArea });
        })), 
        // Handle tab changes by logging the event and fetching preview data.
        this.tabChanges.pipe(tap(tab => {
            if (tab === 'write') {
                eventLogger.log('DiscussionsInputWriteTabSelected');
            }
            else if (tab === 'preview') {
                eventLogger.log('DiscussionsInputPreviewTabSelected');
            }
        }), filter(tab => tab === 'preview'), withLatestFrom(this.textAreaChanges), mergeMap(([, { textAreaValue }]) => of(state => (Object.assign({}, state, { previewHTML: undefined, previewLoading: true }))).pipe(concat(renderMarkdown(this.trimImplicitTitle(textAreaValue)).pipe(map((previewHTML) => state => (Object.assign({}, state, { previewHTML, previewLoading: false }))), catchError((error) => {
            console.error(error);
            return [
                state => (Object.assign({}, state, { error: new Error('Error rendering markdown: ' + error.message), previewLoading: false })),
            ];
        })))))), 
        // Combine form submits and keyboard shortcut submits
        merge(this.submits.pipe(tap(e => e.preventDefault())), 
        // cmd+enter (darwin) or ctrl+enter (linux/win)
        this.textAreaKeyDowns.pipe(filter(e => (e.ctrlKey || e.metaKey) && e.key === 'Enter' && this.canSubmit()))).pipe(withLatestFrom(this.textAreaChanges, this.titleInputChanges.pipe(startWith('')), this.componentUpdates.pipe(startWith(this.props))), mergeMap(([, { textAreaValue }, titleInputValue, props]) => 
        // Start with setting submitting: true
        of(state => (Object.assign({}, state, { submitting: true }))).pipe(concat(props.onSubmit(titleInputValue, this.trimImplicitTitle(textAreaValue)).pipe(map(() => state => (Object.assign({}, state, { submitting: false, titleInputValue: '', textArea: Object.assign({}, state, { textAreaValue: '', selectionStart: 0 }) }))), catchError((error) => {
            console.error(error);
            return [
                state => (Object.assign({}, state, { error: asError(error), submitting: false })),
            ];
        }))))))).subscribe(updateState => this.setState(state => updateState(state)), err => console.error(err)));
        this.componentUpdates.next(this.props);
    }
    componentDidUpdate() {
        this.componentUpdates.next(this.props);
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    render() {
        const { titleInputValue, textArea, error, previewLoading, previewHTML } = this.state;
        return (React.createElement(Form, { className: "discussions-input", onSubmit: this.nextSubmit },
            this.props.titleMode === TitleMode.Explicit && (React.createElement("input", { className: "form-control discussions-input__title", placeholder: "Title", autoFocus: true, onChange: this.nextTitleInputChange, value: titleInputValue })),
            React.createElement(TabsWithLocalStorageViewStatePersistence, { tabs: [{ id: 'write', label: 'Write' }, { id: 'preview', label: 'Preview' }], storageKey: "discussions-input-last-tab", tabBarEndFragment: React.createElement(React.Fragment, null,
                    React.createElement(Spacer, null),
                    React.createElement("small", { className: TabBorderClassName }, "Markdown supported.")), tabClassName: "tab-bar__tab--h5like", onSelectTab: this.nextTabChange },
                React.createElement("div", { key: "write" },
                    textArea.element && (React.createElement(DiscussionsInputMentionOverlay, { location: this.props.location, history: this.props.history, textAreaValue: textArea.textAreaValue, selectionStart: textArea.selectionStart, setTextAreaValue: this.setTextAreaValue, textAreaElement: textArea.element, setOnBlurHandler: this.setOnBlurHandler, setOnKeyDownFilter: this.setOnKeyDownFilter })),
                    React.createElement("textarea", { className: "form-control discussions-input__text-box", placeholder: "Leave a comment", onChange: this.nextTextAreaChange, onKeyDown: this.nextTextAreaKeyDown, onBlur: this.onBlurHandler, value: textArea.textAreaValue, ref: this.setTextAreaRef, autoFocus: this.props.titleMode !== TitleMode.Explicit })),
                React.createElement("div", { key: "preview", className: "discussions-input__preview" },
                    previewLoading && React.createElement(LoadingSpinner, { className: "icon-inline" }),
                    !previewLoading && previewHTML && React.createElement(Markdown, { dangerousInnerHTML: previewHTML }))),
            React.createElement("div", { className: "discussions-input__row" },
                React.createElement("button", { type: "submit", className: "btn btn-primary discussions-input__button", disabled: !this.canSubmit() }, this.props.submitLabel)),
            error && (React.createElement("div", { className: "discussions-input__error alert alert-danger" },
                React.createElement(AlertCircleIcon, { className: "icon-inline discussions-input__error-icon" }),
                error.message))));
    }
}
//# sourceMappingURL=DiscussionsInput.js.map