/*
 * Copyright 2018, 2020 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import Debug from 'debug';
import * as React from 'react';
import { inBrowser, isCursorMovement, History } from '@kui-shell/core';
const debug = Debug('core-support/history/reverse-i-search');
/** state of the reverse-i-search */
export default class ActiveISearch {
    // eslint-disable-next-line no-useless-constructor
    constructor(input, history, currentSearchIdx = -1, reachedTheEnd = false) {
        this.input = input;
        this.history = history;
        this.currentSearchIdx = currentSearchIdx;
        this.reachedTheEnd = reachedTheEnd;
    }
    fixedPart() {
        const className = 'small-right-pad' + (this.reachedTheEnd ? ' alert-pulse' : '');
        if (this.reachedTheEnd) {
            // clear alert-pulse after a second
            setTimeout(() => this.input.setState({ isearch: new ActiveISearch(this.input, this.history, this.currentSearchIdx, false) }), 1000);
        }
        return (React.createElement("span", { className: className },
            "(reverse-i-search",
            React.createElement("span", { className: "sub-text semi-transparent" }, this.currentSearchIdx === -1 ? '' : ' ' + this.currentSearchIdx.toString()),
            ")",
            !this.input.state.prompt.value ? '' : '`' + this.input.state.prompt.value + '`:'));
    }
    matchedPrefixPart(newValue, caretPosition) {
        const matchedPrefix = newValue.substring(0, caretPosition - 1);
        return React.createElement("span", { className: "slightly-deemphasize" }, matchedPrefix);
    }
    matchedSuffixPart(newValue, caretPosition) {
        const matchedSuffix = newValue.substring(caretPosition + this.input.state.prompt.value.length - 1);
        return React.createElement("span", { className: "slightly-deemphasize" }, matchedSuffix);
    }
    typedPart() {
        // show matched whitespaces with an underscore
        return React.createElement("strong", { className: "red-text kui--prompt-like" }, this.input.state.prompt.value.replace(/ /g, '_'));
    }
    matchAt(idx = this.currentSearchIdx) {
        return this.history.line(idx).raw;
    }
    currentMatch() {
        if (this.currentSearchIdx === -1) {
            return '';
        }
        else {
            return this.matchAt();
        }
    }
    render() {
        if (this.currentSearchIdx < 0) {
            return this.fixedPart();
        }
        else {
            const newValue = this.currentMatch();
            const caretPosition = newValue.indexOf(this.input.state.prompt.value) + 1;
            return (React.createElement(React.Fragment, null,
                this.fixedPart(),
                this.matchedPrefixPart(newValue, caretPosition),
                this.typedPart(),
                this.matchedSuffixPart(newValue, caretPosition)));
        }
    }
    /**
     * For various reasons, user has cancelled a reverse-i-search.
     *
     */
    cancelISearch() {
        this.input.setState({ isearch: undefined });
    }
    /**
     * Search command history for a match, skipping over identicals.
     *
     * @return a command history index, or -1 if not match is found
     *
     */
    findPrevious(startIdx) {
        if (startIdx < 0) {
            return -1;
        }
        const { prompt } = this.input.state;
        const newSearchIdx = prompt.value ? this.history.findIndex(prompt.value, startIdx) : -1;
        if (newSearchIdx < 0 || this.currentSearchIdx < 0 || this.matchAt(newSearchIdx) !== this.matchAt()) {
            return newSearchIdx;
        }
        else {
            // skip this match because it is the same as the current match
            return this.findPrevious(newSearchIdx - 1);
        }
    }
    /**
     * Attempt to initiate or extend a search
     *
     */
    doSearch(evt) {
        debug('doSearch', evt);
        // where do we want to start the search? if the user is just
        // typing, then start from the end of history; if the user hit
        // ctrl+r, then they want to search for the next match
        const userHitCtrlR = evt.ctrlKey && evt.key === 'r';
        const startIdx = userHitCtrlR ? this.currentSearchIdx - 1 : undefined;
        const { prompt } = this.input.state;
        const newSearchIdx = this.findPrevious(startIdx);
        debug('search index', prompt.value, newSearchIdx);
        if (newSearchIdx > 0) {
            this.input.setState({ isearch: new ActiveISearch(this.input, this.history, newSearchIdx) });
        }
        else if (userHitCtrlR) {
            // if we found no match, reset the match text, unless the user
            // is using repeated ctrl+R to search backwards; in this case,
            // let's continue to display the previous match if no new match
            // is found
        }
        else {
            this.input.setState({ isearch: new ActiveISearch(this.input, this.history, newSearchIdx, true) });
        }
    }
    /** fill in the result of a search */
    completeSearch() {
        debug('completing search');
        this.input.state.prompt.value = this.currentMatch();
        this.cancelISearch();
    }
}
/**
 * Listen for ctrl+R
 *
 */
export function onKeyUp(evt) {
    return __awaiter(this, void 0, void 0, function* () {
        const activeSearch = this.state.isearch;
        //
        // we want ctrl+R; but if we're in a browser and on linux or
        // windows, then ctrl+R will result in a browser reload :(
        //
        // Note: even if not in a browser (i.e. running in electron mode),
        // on linux and windows we have to be careful not to use the
        // default reload keyboard shortcut; see app/src/main/menu.js
        //
        // re: RUNNING_SHELL_TEST; there seems to be some weird bug here; on linux, the ctrlKey modifier becomes sticky
        if (evt.ctrlKey &&
            (process.platform === 'darwin' ||
                /Macintosh/.test(navigator.userAgent) ||
                (!inBrowser() && !process.env.RUNNING_SHELL_TEST) ||
                evt.metaKey)) {
            if (evt.key === 'r') {
                debug('got ctrl+r');
                if (activeSearch) {
                    debug('continuation of existing reverse-i-search');
                    activeSearch.doSearch(evt);
                }
                else {
                    debug('new reverse-i-search');
                    this.setState({ isearch: new ActiveISearch(this, yield History(this.props.tab)) });
                }
            }
            else if (activeSearch && isCursorMovement(evt.nativeEvent)) {
                activeSearch.completeSearch();
            }
            else if (activeSearch) {
                // with ctrl key down, let any other keycode result in cancelling the outstanding i-search
                debug('cancel', evt.keyCode);
                activeSearch.cancelISearch();
            }
        }
        else if (activeSearch && isCursorMovement(evt.nativeEvent)) {
            activeSearch.completeSearch();
        }
        else if (evt.key === 'Enter' && this.state.isearch) {
            this.state.isearch.completeSearch();
            this.props.tab.REPL.pexec(this.state.isearch.currentMatch() || this.state.prompt.value);
        }
        else if (this.state.isearch && evt.key !== 'Control') {
            evt.preventDefault();
            this.state.isearch.doSearch(evt);
        }
    });
}
//# sourceMappingURL=ActiveISearch.js.map