"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SearchListPrompt = void 0;
const base_1 = __importDefault(require("inquirer/lib/prompts/base"));
const events_1 = __importDefault(require("inquirer/lib/utils/events"));
const paginator_1 = __importDefault(require("inquirer/lib/utils/paginator"));
const figures_1 = __importDefault(require("figures"));
const operators_1 = require("rxjs/operators");
const terminal_1 = require("@rushstack/terminal");
class SearchListPrompt extends base_1.default {
    constructor(question, readline, answers) {
        super(question, readline, answers);
        this._selected = 0;
        this._query = '';
        this._firstRender = true;
        if (!this.opt.choices) {
            this.throwParamError('choices');
        }
        const isDefaultANumber = typeof this.opt.default === 'number';
        if (isDefaultANumber && this.opt.default >= 0 && this.opt.default < this.opt.choices.realLength) {
            this._selected = this.opt.default;
        }
        else if (!isDefaultANumber && this.opt.default !== null) {
            const index = this.opt.choices.realChoices.findIndex(({ value }) => value === this.opt.default);
            this._selected = Math.max(index, 0);
        }
        // Make sure no default is set (so it won't be printed)
        this.opt.default = null;
        this._paginator = new paginator_1.default(this.screen);
    }
    _run(callback) {
        this.done = callback;
        // eslint-disable-next-line @typescript-eslint/typedef
        const events = (0, events_1.default)(this.rl);
        // eslint-disable-next-line @typescript-eslint/typedef
        const validation = this.handleSubmitEvents(events.line.pipe((0, operators_1.map)(this._getCurrentValue.bind(this))));
        void validation.success.forEach(this._onSubmit.bind(this));
        void validation.error.forEach(this._onError.bind(this));
        void events.numberKey
            .pipe((0, operators_1.takeUntil)(events.line))
            .forEach(this._onNumberKey.bind(this));
        void events.keypress
            .pipe((0, operators_1.takeUntil)(validation.success))
            .forEach(this._onKeyPress.bind(this));
        this.render();
        return this;
    }
    _onUpKey() {
        return this._adjustSelected(-1);
    }
    _onDownKey() {
        return this._adjustSelected(1);
    }
    _onNumberKey(input) {
        if (input <= this.opt.choices.realLength) {
            this._selected = input - 1;
        }
        this.render();
    }
    /**
     * When user press `enter` key
     */
    _onSubmit(state) {
        this.status = 'answered';
        // Rerender prompt (and clean subline error)
        this.render();
        this.screen.done();
        this.done(state.value);
    }
    _onError(state) {
        this.render(state.isValid || undefined);
    }
    _onKeyPress(event) {
        var _a, _b;
        if (event.key.ctrl) {
            switch (event.key.name) {
                case 'backspace':
                    return this._setQuery('');
            }
        }
        else {
            switch (event.key.name) {
                // Go to beginning of list
                case 'home':
                    return this._adjustSelected(-Infinity);
                // Got to end of list
                case 'end':
                    return this._adjustSelected(Infinity);
                // Paginate up
                case 'pageup':
                    return this._adjustSelected(-((_a = this.opt.pageSize) !== null && _a !== void 0 ? _a : 1));
                // Paginate down
                case 'pagedown':
                    return this._adjustSelected((_b = this.opt.pageSize) !== null && _b !== void 0 ? _b : 1);
                case 'backspace':
                    return this._setQuery(this._query.slice(0, -1));
                case 'up':
                    return this._onUpKey();
                case 'down':
                    return this._onDownKey();
                default:
                    if (event.key.sequence && event.key.sequence.length === 1) {
                        this._setQuery(this._query + event.key.sequence);
                    }
            }
        }
    }
    _setQuery(query) {
        this._query = query;
        const filter = query.toUpperCase();
        const { choices } = this.opt.choices;
        for (const choice of choices) {
            if (choice.type !== 'separator') {
                choice.disabled = !choice.short.toUpperCase().includes(filter);
            }
        }
        // Select the first valid option
        this._adjustSelected(0);
    }
    // Provide the delta in deplayed choices and change the selected
    // index accordingly by the delta in real choices
    _adjustSelected(delta) {
        const { choices } = this.opt.choices;
        const pointer = this._selected;
        let lastValidIndex = pointer;
        // if delta is less than 0, we are moving up in list w/ selected index
        if (delta < 0) {
            for (let i = pointer - 1; i >= 0; i--) {
                const choice = choices[i];
                if (isValidChoice(choice)) {
                    ++delta;
                    lastValidIndex = i;
                    // if delta is 0, we have found the next valid choice that has an index less than the selected index
                    if (delta === 0) {
                        break;
                    }
                }
            }
        }
        else {
            // if delta is greater than 0, we are moving down in list w/ selected index
            // Also, if delta is exactly 0, the request is to adjust to the first
            // displayed choice that has an index >= the current selected choice.
            ++delta;
            for (let i = pointer, len = choices.length; i < len; i++) {
                const choice = choices[i];
                if (isValidChoice(choice)) {
                    --delta;
                    lastValidIndex = i;
                    // if delta is 0, we have found the next valid choice that has an index greater than the selected index
                    if (delta === 0) {
                        break;
                    }
                }
            }
        }
        this._selected = lastValidIndex;
        this.render();
    }
    _getCurrentValue() {
        return this.opt.choices.getChoice(this._selected).value;
    }
    render(error) {
        // Render the question
        let message = this.getQuestion();
        let bottomContent = '';
        if (this._firstRender) {
            message += terminal_1.Colorize.dim(' (Use arrow keys)');
        }
        // Render choices or answer depending on the state
        if (this.status === 'answered') {
            message += terminal_1.Colorize.cyan(this.opt.choices.getChoice(this._selected).short);
        }
        else {
            const choicesStr = listRender(this.opt.choices, this._selected);
            const indexPosition = this.opt.choices.indexOf(this.opt.choices.getChoice(this._selected));
            let realIndexPosition = 0;
            const { choices } = this.opt.choices;
            for (let i = 0; i < indexPosition; i++) {
                const value = choices[i];
                // Add line if it's a separator
                if (value.type === 'separator') {
                    realIndexPosition++;
                    continue;
                }
                // Do not render choices which disabled property
                // these represent choices that are filtered out
                if (value.disabled) {
                    continue;
                }
                const line = value.name;
                // Non-strings take up one line
                if (typeof line !== 'string') {
                    realIndexPosition++;
                    continue;
                }
                // Calculate lines taken up by string
                // eslint-disable-next-line no-bitwise
                realIndexPosition += ((line.length / process.stdout.columns) | 0) + 1;
            }
            message += `\n${terminal_1.Colorize.white(terminal_1.Colorize.bold('Start typing to filter:'))} ${terminal_1.Colorize.cyan(this._query)}`;
            // @ts-expect-error Types are wrong
            message += '\n' + this._paginator.paginate(choicesStr, realIndexPosition, this.opt.pageSize);
        }
        if (error) {
            bottomContent = terminal_1.Colorize.red('>> ') + error;
        }
        this.screen.render(message, bottomContent);
    }
}
exports.SearchListPrompt = SearchListPrompt;
function listRender(choices, pointer) {
    let output = '';
    choices.forEach((choice, i) => {
        if (choice.type === 'separator') {
            output += ' ' + choice + '\n';
            return;
        }
        if (!choice.disabled) {
            const line = choice.name;
            if (i === pointer) {
                output += terminal_1.Colorize.cyan(figures_1.default.pointer + line);
            }
            else {
                output += ' ' + line;
            }
        }
        if (i < choices.length - 1) {
            output += '\n';
        }
    });
    return output.replace(/\n$/, '');
}
function isValidChoice(choice) {
    return !choice.disabled;
}
//# sourceMappingURL=SearchListPrompt.js.map