import { LoadingSpinner } from '@sourcegraph/react-loading-spinner';
import * as React from 'react';
import { fromEvent, merge, Observable, of, Subject, Subscription } from 'rxjs';
import { catchError, debounceTime, delay, distinctUntilChanged, filter, map, publishReplay, refCount, repeat, startWith, switchMap, takeUntil, tap, toArray, } from 'rxjs/operators';
import { Key } from 'ts-key-enum';
import { eventLogger } from '../../tracking/eventLogger';
import { scrollIntoView } from '../../util';
import { fetchSuggestions } from '../backend';
import { createSuggestion, SuggestionItem } from './Suggestion';
/**
 * The query input field is clobbered and updated to contain this subject's values, as
 * they are received. This is used to trigger an update; the source of truth is still the URL.
 *
 * This file is mostly the same as Queryinput.tsx but differs for the use on the homepage on line
 * 326-335. It also does not need onInputFocus function.
 *
 */
export const queryUpdates = new Subject();
export class QueryInputForModal extends React.Component {
    constructor(props) {
        super(props);
        this.componentUpdates = new Subject();
        /** Subscriptions to unsubscribe from on component unmount */
        this.subscriptions = new Subscription();
        /** Emits on keydown events in the input field */
        this.inputKeyDowns = new Subject();
        /** Emits new input values */
        this.inputValues = new Subject();
        /** Emits when the input field is clicked */
        this.inputFocuses = new Subject();
        /** Emits when the suggestions are hidden */
        this.suggestionsHidden = new Subject();
        /** Only used to keep track if the user has typed a single character into the input field so we can log an event once. */
        this.hasLoggedFirstInput = false;
        // Handle the cursor changing in the input field and
        // allow the user to type after the string changes.
        // Used in modal on homepage.
        this.changeCursor = e => {
            setTimeout(() => {
                this.inputFocuses.next();
                this.setState({ inputFocused: true });
            }, 100);
        };
        this.setSuggestionListElement = (ref) => {
            this.suggestionListElement = ref || undefined;
        };
        this.selectSuggestion = (suggestion) => {
            // 🚨 PRIVACY: never provide any private data in { code_search: { suggestion: { type } } }.
            eventLogger.log('SearchSuggestionSelected', {
                code_search: {
                    suggestion: {
                        type: suggestion.type,
                        url: suggestion.url,
                    },
                },
            });
            this.props.history.push(suggestion.url);
            this.suggestionsHidden.next();
            this.setState({ hideSuggestions: true, selectedSuggestion: -1 });
        };
        this.onInputChange = event => {
            if (!this.hasLoggedFirstInput) {
                eventLogger.log('SearchInitiated');
                this.hasLoggedFirstInput = true;
            }
            this.inputValues.next(event.currentTarget.value);
        };
        this.onInputBlur = event => {
            this.suggestionsHidden.next();
            this.setState({ inputFocused: false, loading: false, hideSuggestions: true });
        };
        this.onInputKeyDown = event => {
            event.persist();
            this.inputKeyDowns.next(event);
            switch (event.key) {
                case Key.Escape: {
                    this.suggestionsHidden.next();
                    this.setState({ loading: false, hideSuggestions: true, selectedSuggestion: -1 });
                    break;
                }
                case Key.ArrowDown: {
                    event.preventDefault();
                    this.moveSelection(1);
                    break;
                }
                case Key.ArrowUp: {
                    event.preventDefault();
                    this.moveSelection(-1);
                    break;
                }
                case Key.Enter: {
                    if (this.state.selectedSuggestion === -1) {
                        // Submit form and hide suggestions
                        this.suggestionsHidden.next();
                        this.setState({ hideSuggestions: true });
                        break;
                    }
                    // Select suggestion
                    event.preventDefault();
                    if (this.state.suggestions.length === 0) {
                        break;
                    }
                    this.selectSuggestion(this.state.suggestions[Math.max(this.state.selectedSuggestion, 0)]);
                    this.setState({ hideSuggestions: true });
                    break;
                }
            }
        };
        this.state = {
            hideSuggestions: false,
            inputFocused: false,
            loading: false,
            selectedSuggestion: -1,
            suggestions: [],
        };
        this.subscriptions.add(
        // Trigger new suggestions every time the input field is typed into
        this.inputValues
            .pipe(tap(query => this.props.onChange(query)), distinctUntilChanged(), debounceTime(200), switchMap(query => {
            if (query.length < QueryInputForModal.SUGGESTIONS_QUERY_MIN_LENGTH) {
                return [{ suggestions: [], selectedSuggestion: -1, loading: false }];
            }
            const options = {
                query: [this.props.prependQueryForSuggestions, this.props.value].filter(s => !!s).join(' '),
            };
            const suggestionsFetch = fetchSuggestions(options).pipe(map(createSuggestion), toArray(), map((suggestions) => ({
                suggestions,
                selectedSuggestion: -1,
                hideSuggestions: false,
                loading: false,
            })), catchError((err) => {
                console.error(err);
                this.setState({ loading: false });
                // HACK: if we catchError before 100ms, then the loader will display over us.
                // This is not a good fix.
                setTimeout(() => this.setState({ loading: false }), 120);
                return [{}];
            }), publishReplay(), refCount());
            return merge(suggestionsFetch, 
            // Show a loader if the fetch takes longer than 100ms
            of({ loading: true }).pipe(delay(100), takeUntil(suggestionsFetch)));
        }), 
        // Abort suggestion display on route change or suggestion hiding
        takeUntil(this.suggestionsHidden), 
        // But resubscribe afterwards
        repeat())
            .subscribe(state => {
            this.setState(state);
        }, err => {
            this.setState({ loading: false });
            console.error(err);
        }));
        if (this.props.hasGlobalQueryBehavior) {
            // Quick-Open hotkeys
            this.subscriptions.add(fromEvent(window, 'keydown')
                .pipe(filter(event => 
            // Slash shortcut (if no input element is focused)
            (event.key === '/' &&
                !['INPUT', 'TEXTAREA'].includes(document.activeElement.nodeName)) ||
                // Cmd/Ctrl+P shortcut
                ((event.metaKey || event.ctrlKey) && event.key === 'p') ||
                // Cmd/Ctrl+Shift+F shortcut
                ((event.metaKey || event.ctrlKey) && event.shiftKey && event.key === 'f')), switchMap(event => {
                event.preventDefault();
                // Use selection as query
                const selection = window.getSelection().toString();
                if (selection) {
                    return new Observable(observer => this.setState({
                        // query: selection, TODO(sqs): add back this behavior
                        suggestions: [],
                        selectedSuggestion: -1,
                    }, () => {
                        observer.next();
                        observer.complete();
                    }));
                }
                return [undefined];
            }))
                .subscribe(() => {
                if (this.inputElement) {
                    // Select all input
                    this.inputElement.focus();
                    this.inputElement.setSelectionRange(0, this.inputElement.value.length);
                }
            }));
            // Allow other components to update the query (e.g., to be relevant to what the user is
            // currently viewing).
            this.subscriptions.add(queryUpdates.pipe(distinctUntilChanged()).subscribe(query => this.props.onChange(query)));
            /** Whenever the URL query has a "focus" property, remove it and focus the query input. */
            this.subscriptions.add(this.componentUpdates.pipe(startWith(props)).subscribe(props => {
                if (this.inputElement) {
                    const value = this.inputElement.value;
                    if (value !== props.value) {
                        this.inputElement.value = props.value;
                        this.focusInputAndPositionCursorAtEnd();
                    }
                }
            }));
        }
    }
    componentDidMount() {
        switch (this.props.autoFocus) {
            case 'cursor-at-end':
                this.focusInputAndPositionCursorAtEnd();
                break;
        }
    }
    componentWillReceiveProps(newProps) {
        this.componentUpdates.next(newProps);
    }
    componentWillUnmount() {
        this.subscriptions.unsubscribe();
    }
    componentDidUpdate(prevProps, prevState) {
        // Check if selected suggestion is out of view
        scrollIntoView(this.suggestionListElement, this.selectedSuggestionElement);
    }
    render() {
        const showSuggestions = this.props.value.length >= QueryInputForModal.SUGGESTIONS_QUERY_MIN_LENGTH &&
            this.state.inputFocused &&
            !this.state.hideSuggestions &&
            this.state.suggestions.length !== 0;
        return (React.createElement("div", { className: "query-input2-for-modal" },
            React.createElement("input", { className: "form-control query-input2-for-modal__input", defaultValue: this.props.value, autoFocus: this.props.autoFocus === true, onChange: this.onInputChange, onKeyDown: this.onInputKeyDown, onFocus: this.changeCursor, onBlur: this.onInputBlur, spellCheck: false, autoCapitalize: "off", placeholder: this.props.placeholder === undefined ? 'Search code...' : this.props.placeholder, ref: ref => (this.inputElement = ref) }),
            this.state.loading && React.createElement(LoadingSpinner, { className: "icon-inline query-input2__loading-notifier" }),
            showSuggestions && (React.createElement("ul", { className: "query-input2-for-modal__suggestions", ref: this.setSuggestionListElement }, this.state.suggestions.map((suggestion, i) => {
                const isSelected = this.state.selectedSuggestion === i;
                const onRef = (ref) => {
                    if (isSelected) {
                        this.selectedSuggestionElement = ref || undefined;
                    }
                };
                return (React.createElement(SuggestionItem, { key: i, suggestion: suggestion, isSelected: isSelected, 
                    // tslint:disable-next-line:jsx-no-lambda
                    onClick: () => this.selectSuggestion(suggestion), liRef: onRef }));
            })))));
    }
    focusInputAndPositionCursorAtEnd() {
        if (this.inputElement) {
            // Focus the input element and set cursor to the end
            this.inputElement.focus();
            this.inputElement.setSelectionRange(this.inputElement.value.length, this.inputElement.value.length);
        }
    }
    moveSelection(steps) {
        this.setState({
            selectedSuggestion: Math.max(Math.min(this.state.selectedSuggestion + steps, this.state.suggestions.length - 1), -1),
        });
    }
}
QueryInputForModal.SUGGESTIONS_QUERY_MIN_LENGTH = 2;
//# sourceMappingURL=QueryInputForModal.js.map