"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandLineUI = void 0;
const tslib_1 = require("tslib");
const cli_table3_1 = tslib_1.__importDefault(require("cli-table3"));
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
const ora_1 = tslib_1.__importDefault(require("ora"));
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const yaml_1 = tslib_1.__importDefault(require("yaml"));
const log_color_1 = require("./log-color");
const log_symbols_1 = tslib_1.__importDefault(require("./log-symbols"));
const logger_1 = require("./logger");
const text_1 = require("./text");
const multiple_table_prompt_1 = require("./multiple-table-prompt");
const single_table_prompt_1 = require("./single-table-prompt");
const shared_1 = require("../shared");
const cache_1 = require("../cache");
var ProgressLogType;
(function (ProgressLogType) {
    ProgressLogType[ProgressLogType["startText"] = 0] = "startText";
    ProgressLogType[ProgressLogType["successText"] = 1] = "successText";
    ProgressLogType[ProgressLogType["other"] = 2] = "other";
})(ProgressLogType || (ProgressLogType = {}));
class CommandLineUI {
    verbose;
    logger;
    spinner;
    promptInternal;
    insideProgress = false;
    addedProgressPadding = false;
    constructor(verbose, spinner, logger) {
        this.verbose = verbose;
        this.spinner = spinner || (0, ora_1.default)({ discardStdin: false });
        this.logger = logger || console;
        this.promptInternal = inquirer_1.default.createPromptModule({ skipTTYChecks: false });
        this.registerCustomUIElements();
    }
    getSeasonalSpinner() {
        if (process.env.FORGE_DISABLE_SEASONAL_EFFECTS) {
            return 'dots';
        }
        try {
            const cachedConf = cache_1.CachedConf.getCache(cache_1.CONFIG_PROJECT_NAME);
            const seasonalEffectsEnabled = cachedConf.get('seasonal-effects') ?? true;
            if (!seasonalEffectsEnabled) {
                return 'dots';
            }
        }
        catch {
        }
        const now = new Date();
        const month = now.getMonth();
        if (month === 9) {
            return {
                interval: 300,
                frames: ['🎃', '🟠', '⚫', '🟤']
            };
        }
        return 'dots';
    }
    async prompt(questions, initialAnswers) {
        try {
            return await this.promptInternal(questions, initialAnswers);
        }
        catch (e) {
            throw new shared_1.UIPromptError(e.message, e.isTtyError ?? false);
        }
    }
    get traceEnabled() {
        return this.verbose();
    }
    get debugEnabled() {
        return this.verbose();
    }
    async displayProgress(progress, startText, successText) {
        try {
            this.insideProgress = true;
            this.addedProgressPadding = false;
            if (!this.debugEnabled) {
                this.spinner.spinner = this.getSeasonalSpinner();
                this.spinner.start();
            }
            this.log(startText, ProgressLogType.startText);
            const result = await progress();
            const progressResult = typeof successText === 'function' ? successText(result) : successText;
            const isSuccessful = typeof progressResult === 'string' || progressResult.successful;
            if (isSuccessful) {
                this.logSuccess(progressResult.message || progressResult);
            }
            else {
                this.logError(progressResult.message);
            }
            this.stopProgressSpinner(isSuccessful);
            return result;
        }
        catch (err) {
            this.stopProgressSpinner(false);
            throw err;
        }
        finally {
            this.insideProgress = false;
        }
    }
    async displayTemporaryMessage(progress, waitText) {
        try {
            this.insideProgress = true;
            this.addedProgressPadding = false;
            if (this.debugEnabled) {
                this.log(waitText, ProgressLogType.startText);
            }
            else {
                this.spinner.spinner = { interval: 9999, frames: [''] };
                this.spinner.prefixText = waitText;
                this.spinner.start();
            }
            const result = await progress();
            this.stopWaitSpinner(true);
            return result;
        }
        catch (err) {
            this.stopWaitSpinner(false);
            throw err;
        }
        finally {
            this.insideProgress = false;
        }
    }
    trace(message) {
        if (!this.traceEnabled) {
            return;
        }
        const color = log_color_1.LogColor.trace(message);
        const msg = this.insideProgress ? '  ' + color : color;
        this.log(msg);
    }
    debug(message) {
        if (!this.debugEnabled) {
            return;
        }
        const color = log_color_1.LogColor.debug(message);
        const msg = this.insideProgress ? '  ' + color : color;
        this.log(msg);
    }
    info(message) {
        const msg = this.insideProgress ? log_symbols_1.default.info + ' ' + message : message;
        this.log(msg);
    }
    warn(message) {
        const color = log_color_1.LogColor.warn(message);
        const msg = this.insideProgress ? log_symbols_1.default.warning + ' ' + color : color;
        this.log(msg);
    }
    error(error, { pad = true } = {}) {
        const message = (0, logger_1.errorMessage)(error);
        const color = log_color_1.LogColor.error(message);
        let msg = '\n' + log_color_1.LogColor.errorPrefix + color + '\n';
        if (!pad) {
            msg = msg.trim();
        }
        this.log(msg);
        if (error.stack) {
            this.debug(error.stack);
        }
    }
    emptyLine() {
        this.log('');
    }
    async confirm(message) {
        const { choice } = await this.prompt([
            {
                type: 'confirm',
                name: 'choice',
                message,
                default: false
            }
        ]);
        return choice;
    }
    promptForText(message, defaultValue) {
        return this.promptForString(message, false, defaultValue);
    }
    promptForSecret(message) {
        return this.promptForString(message, true);
    }
    async promptForList(message, choices, config, pageSize) {
        const formattedChoices = config?.format ? choices.map((choice) => config?.format?.(choice)) : choices;
        const { choice } = await this.prompt([
            {
                type: 'list',
                name: 'choice',
                choices: formattedChoices,
                message,
                pageSize
            }
        ]);
        const formattedChoice = config?.format ? choices[formattedChoices.indexOf(choice)] : choice;
        return formattedChoice;
    }
    async promptForTable(message, infoMessage, columns, choices, isBorderless) {
        const { choice } = await this.prompt([
            {
                type: 'multiple-choice-table',
                name: 'choice',
                message,
                infoMessage,
                columns,
                choices,
                isBorderless
            }
        ]);
        return choice;
    }
    async promptForSingleChoiceTable(message, infoMessage, columns, choices) {
        const { choice } = await this.prompt([
            {
                type: 'single-choice-table',
                name: 'choice',
                message,
                infoMessage,
                columns,
                choices
            }
        ]);
        return choice;
    }
    table(head, data, { json, emptyMessage, preMessage, postMessage, format, groupRows } = {}) {
        if (json) {
            this.log(JSON.stringify(data, null, 2));
            return;
        }
        if (!data?.length && emptyMessage) {
            this.info(emptyMessage);
            return;
        }
        if (preMessage) {
            this.info(preMessage);
        }
        this.logTable(head, data, { format, groupRows });
        if (postMessage) {
            this.info(postMessage);
        }
    }
    formatTableValue(key, value, format = {}) {
        const formatter = format[key];
        return formatter ? formatter(value) : value;
    }
    shouldLogTableCell(key, formattedValue, prevRow, format, groupRows = {}) {
        if (!groupRows[key]) {
            return true;
        }
        if (!prevRow) {
            return true;
        }
        const prevValue = prevRow[key];
        const prevFormattedValue = this.formatTableValue(key, prevValue, format);
        return formattedValue !== prevFormattedValue;
    }
    logTable(head, data, { format, groupRows }) {
        const table = new cli_table3_1.default({
            ...log_color_1.TableStyle.default,
            head: head.map(([, heading]) => heading)
        });
        if (data) {
            const rowSpanByKey = {};
            for (let i = data.length - 1; i >= 0; i--) {
                const tableRow = [];
                head.forEach(([key]) => {
                    rowSpanByKey[key] = rowSpanByKey[key] || 1;
                    const row = data[i];
                    const value = row[key];
                    const formattedValue = this.formatTableValue(key, value, format);
                    if (this.shouldLogTableCell(key, formattedValue, data[i - 1], format, groupRows)) {
                        tableRow.push({
                            rowSpan: rowSpanByKey[key],
                            content: formattedValue
                        });
                        rowSpanByKey[key] = 1;
                    }
                    else {
                        rowSpanByKey[key]++;
                    }
                });
                table.unshift(tableRow);
            }
        }
        this.info(table.toString());
    }
    log(message, progressLogType = ProgressLogType.other) {
        let msg = message;
        if (this.insideProgress) {
            const addPadding = (progressLogType === ProgressLogType.other && !this.addedProgressPadding) ||
                (progressLogType === ProgressLogType.successText && this.addedProgressPadding);
            if (addPadding) {
                msg = '\n' + msg;
                this.addedProgressPadding = true;
            }
        }
        if (this.spinner.isSpinning) {
            this.spinner.text += msg + '\n';
        }
        else {
            this.logger.log(msg);
        }
    }
    logSuccess(successText) {
        if (this.addedProgressPadding) {
            this.log(log_symbols_1.default.success + ' ' + successText, ProgressLogType.successText);
        }
        else {
            this.replaceStartTextOnSuccess(successText);
        }
    }
    logError(errorText) {
        if (this.spinner.isSpinning) {
            this.spinner.text = errorText;
        }
        else {
            this.log(log_symbols_1.default.error + ' ' + errorText, ProgressLogType.other);
        }
    }
    stopProgressSpinner(success) {
        if (!this.spinner.isSpinning) {
            return;
        }
        if (this.spinner.text.endsWith('\n')) {
            this.spinner.text = this.spinner.text.substring(0, this.spinner.text.length - 1);
        }
        const symbol = success ? log_symbols_1.default.success : log_symbols_1.default.error;
        this.spinner.stopAndPersist({ symbol });
    }
    replaceStartTextOnSuccess(successText) {
        if (this.spinner.isSpinning) {
            this.spinner.text = successText;
        }
        else {
            this.log(log_symbols_1.default.success + ' ' + successText, ProgressLogType.successText);
        }
    }
    stopWaitSpinner(success) {
        if (!this.spinner.isSpinning) {
            return;
        }
        if (success) {
            this.spinner.prefixText = '';
            this.spinner.stop();
        }
        else {
            this.spinner.stopAndPersist();
        }
    }
    clearSpinner() {
        this.spinner.text = '';
    }
    async promptForString(message, secret, defaultValue) {
        const { text } = await this.prompt([
            {
                type: secret ? 'password' : 'input',
                name: 'text',
                message,
                default: defaultValue,
                validate: (input) => (input.length === 0 ? text_1.Text.error.promptInputRequired : true)
            }
        ]);
        return text;
    }
    registerCustomUIElements() {
        this.promptInternal.registerPrompt('multiple-choice-table', multiple_table_prompt_1.MultipleChoiceTablePrompt);
        this.promptInternal.registerPrompt('single-choice-table', single_table_prompt_1.SingleChoiceTablePrompt);
    }
    formatKeyValueList(items, indent, addNewLine) {
        const formattedItems = items.map(({ key, value }) => {
            const newLineOrSpace = addNewLine ? '\n ' : ' ';
            return `[${chalk_1.default.cyan(key)}]:${newLineOrSpace}${value}`;
        });
        return (0, text_1.itemList)(formattedItems, indent);
    }
    formatYamlProperties(properties, key, showMessage = true) {
        const yamlString = yaml_1.default.stringify(properties, undefined, '  ').replaceAll('\n', '\n  ');
        return `[${chalk_1.default.cyan(key)}]: ${showMessage ? 'is configured with the following properties:' : ''}\n  ${yamlString}`;
    }
}
exports.CommandLineUI = CommandLineUI;
