"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.RushAlerts = void 0;
const terminal_1 = require("@rushstack/terminal");
const node_core_library_1 = require("@rushstack/node-core-library");
const rush_alerts_schema_json_1 = __importDefault(require("../schemas/rush-alerts.schema.json"));
const RushConstants_1 = require("../logic/RushConstants");
const actionNameConstants_1 = require("./actionNameConstants");
var AlertDisplayInterval;
(function (AlertDisplayInterval) {
    AlertDisplayInterval["ALWAYS"] = "always";
    AlertDisplayInterval["MONTHLY"] = "monthly";
    AlertDisplayInterval["WEEKLY"] = "weekly";
    AlertDisplayInterval["DAILY"] = "daily";
    AlertDisplayInterval["HOURLY"] = "hourly";
})(AlertDisplayInterval || (AlertDisplayInterval = {}));
var AlertPriority;
(function (AlertPriority) {
    AlertPriority["HIGH"] = "high";
    AlertPriority["NORMAL"] = "normal";
    AlertPriority["LOW"] = "low";
})(AlertPriority || (AlertPriority = {}));
class RushAlerts {
    constructor(options) {
        var _a;
        this._terminal = options.terminal;
        this._rushJsonFolder = options.rushJsonFolder;
        this.rushAlertsStateFilePath = options.rushAlertsStateFilePath;
        this.rushAlertsConfigFilePath = options.rushAlertsConfigFilePath;
        this._rushAlertsConfig = options.rushAlertsConfig;
        this._rushAlertsState = (_a = options.rushAlertsState) !== null && _a !== void 0 ? _a : {};
    }
    static async loadFromConfigurationAsync(rushConfiguration, terminal) {
        const rushAlertsStateFilePath = `${rushConfiguration.commonTempFolder}/${RushConstants_1.RushConstants.rushAlertsConfigFilename}`;
        const rushAlertsConfigFilePath = `${rushConfiguration.commonRushConfigFolder}/${RushConstants_1.RushConstants.rushAlertsConfigFilename}`;
        const rushJsonFolder = rushConfiguration.rushJsonFolder;
        const [isRushAlertsStateFileExists, isRushAlertsConfigFileExists] = await Promise.all([
            node_core_library_1.FileSystem.existsAsync(rushAlertsStateFilePath),
            node_core_library_1.FileSystem.existsAsync(rushAlertsConfigFilePath)
        ]);
        const [rushAlertsConfig, rushAlertsState] = await Promise.all([
            isRushAlertsConfigFileExists
                ? node_core_library_1.JsonFile.loadAndValidateAsync(rushAlertsConfigFilePath, node_core_library_1.JsonSchema.fromLoadedObject(rush_alerts_schema_json_1.default))
                : undefined,
            isRushAlertsStateFileExists
                ? node_core_library_1.JsonFile.loadAsync(rushAlertsStateFilePath, { jsonSyntax: node_core_library_1.JsonSyntax.JsonWithComments })
                : undefined
        ]);
        return new RushAlerts({
            terminal,
            rushAlertsStateFilePath,
            rushAlertsConfigFilePath,
            rushJsonFolder,
            rushAlertsConfig,
            rushAlertsState
        });
    }
    _ensureAlertStateIsUpToDate() {
        // ensure `temp/rush-alerts.json` is up to date
        if (this._rushAlertsConfig) {
            for (const alert of this._rushAlertsConfig.alerts) {
                if (!(alert.alertId in this._rushAlertsState)) {
                    this._rushAlertsState[alert.alertId] = {
                        snooze: false
                    };
                }
            }
        }
    }
    async printAlertsAsync() {
        if (!this._rushAlertsConfig || this._rushAlertsConfig.alerts.length === 0)
            return;
        this._ensureAlertStateIsUpToDate();
        this._terminal.writeLine();
        const alert = await this._selectAlertByPriorityAsync();
        if (alert) {
            this._printMessageInBoxStyle(alert);
            this._rushAlertsState[alert.alertId].lastDisplayTime = new Date().toISOString();
        }
        await this._writeRushAlertStateAsync();
    }
    async printAllAlertsAsync() {
        var _a, _b;
        const allAlerts = (_b = (_a = this._rushAlertsConfig) === null || _a === void 0 ? void 0 : _a.alerts) !== null && _b !== void 0 ? _b : [];
        const activeAlerts = [];
        const snoozedAlerts = [];
        const inactiveAlerts = [];
        await Promise.all(allAlerts.map(async (alert) => {
            const isAlertValid = await this._isAlertValidAsync(alert);
            const alertState = this._rushAlertsState[alert.alertId];
            if (!isAlertValid) {
                inactiveAlerts.push(alert);
                return;
            }
            if (this._isSnoozing(alertState)) {
                snoozedAlerts.push(alert);
                return;
            }
            activeAlerts.push(alert);
        }));
        this._printAlerts(activeAlerts, 'active');
        this._printAlerts(snoozedAlerts, 'snoozed');
        this._printAlerts(inactiveAlerts, 'inactive');
    }
    _printAlerts(alerts, status) {
        if (alerts.length === 0)
            return;
        switch (status) {
            case 'active':
            case 'inactive':
                this._terminal.writeLine(terminal_1.Colorize.yellow(`The following alerts are currently ${status}:`));
                break;
            case 'snoozed':
                this._terminal.writeLine(terminal_1.Colorize.yellow('The following alerts are currently active but snoozed:'));
                break;
        }
        alerts.forEach(({ title }) => {
            this._terminal.writeLine(terminal_1.Colorize.green(`"${title}"`));
        });
        this._terminal.writeLine();
    }
    async snoozeAlertsByAlertIdAsync(alertId, forever = false) {
        this._ensureAlertStateIsUpToDate();
        if (forever) {
            this._rushAlertsState[alertId].snooze = true;
        }
        else {
            this._rushAlertsState[alertId].snooze = true;
            const snoozeEndTime = new Date();
            snoozeEndTime.setDate(snoozeEndTime.getDate() + 7);
            this._rushAlertsState[alertId].snoozeEndTime = snoozeEndTime.toISOString();
        }
        await this._writeRushAlertStateAsync();
    }
    async _selectAlertByPriorityAsync() {
        const alerts = this._rushAlertsConfig.alerts;
        const alertsState = this._rushAlertsState;
        const needDisplayAlerts = (await Promise.all(alerts.map(async (alert) => {
            var _a;
            const isAlertValid = await this._isAlertValidAsync(alert);
            const alertState = alertsState[alert.alertId];
            if (isAlertValid &&
                !this._isSnoozing(alertState) &&
                (!alertState.lastDisplayTime ||
                    Number(new Date()) - Number(new Date(alertState.lastDisplayTime)) >
                        RushAlerts.alertDisplayIntervalDurations.get((_a = alert.maximumDisplayInterval) !== null && _a !== void 0 ? _a : AlertDisplayInterval.ALWAYS))) {
                return alert;
            }
        }))).filter((alert) => alert !== undefined);
        const alertsSortedByPriority = needDisplayAlerts.sort((a, b) => {
            var _a, _b;
            return (RushAlerts.ALERT_PRIORITY.indexOf((_a = a.priority) !== null && _a !== void 0 ? _a : AlertPriority.NORMAL) -
                RushAlerts.ALERT_PRIORITY.indexOf((_b = b.priority) !== null && _b !== void 0 ? _b : AlertPriority.NORMAL));
        });
        return alertsSortedByPriority[0];
    }
    static _parseDate(dateString) {
        const parsedDate = new Date(dateString);
        if (isNaN(parsedDate.getTime())) {
            throw new Error(`Invalid date/time value ${JSON.stringify(dateString)}`);
        }
        return parsedDate;
    }
    _isSnoozing(alertState) {
        return (Boolean(alertState.snooze) &&
            (!alertState.snoozeEndTime || Number(new Date()) < Number(new Date(alertState.snoozeEndTime))));
    }
    async _isAlertValidAsync(alert) {
        const timeNow = new Date();
        if (alert.startTime) {
            const startTime = RushAlerts._parseDate(alert.startTime);
            if (timeNow < startTime) {
                return false;
            }
        }
        if (alert.endTime) {
            const endTime = RushAlerts._parseDate(alert.endTime);
            if (timeNow > endTime) {
                return false;
            }
        }
        const conditionScript = alert.conditionScript;
        if (conditionScript) {
            // "(OPTIONAL) The filename of a script that determines whether this alert can be shown,
            // found in the "common/config/rush/alert-scripts" folder." ... "To ensure up-to-date alerts, Rush
            // may fetch and checkout the "common/config/rush-alerts" folder in an unpredictable temporary
            // path.  Therefore, your script should avoid importing dependencies from outside its folder,
            // generally be kept as simple and reliable and quick as possible."
            if (conditionScript.indexOf('/') >= 0 || conditionScript.indexOf('\\') >= 0) {
                throw new Error(`The rush-alerts.json file contains a "conditionScript" that is not inside the "alert-scripts" folder: ` +
                    JSON.stringify(conditionScript));
            }
            const conditionScriptPath = `${this._rushJsonFolder}/common/config/rush/alert-scripts/${conditionScript}`;
            if (!(await node_core_library_1.FileSystem.existsAsync(conditionScriptPath))) {
                throw new Error('The "conditionScript" field in rush-alerts.json refers to a nonexistent file:\n' +
                    conditionScriptPath);
            }
            this._terminal.writeDebugLine(`Invoking condition script "${conditionScript}" from rush-alerts.json`);
            const startTimemark = performance.now();
            let conditionScriptModule;
            try {
                conditionScriptModule = require(conditionScriptPath);
                if (typeof conditionScriptModule.canShowAlert !== 'function') {
                    throw new Error('The "canShowAlert" module export is missing');
                }
            }
            catch (e) {
                throw new Error(`Error loading condition script "${conditionScript}" from rush-alerts.json:\n${e.stack}`);
            }
            const oldCwd = process.cwd();
            let conditionResult;
            try {
                // "Rush will invoke this script with the working directory set to the monorepo root folder,
                // with no guarantee that `rush install` has been run."
                process.chdir(this._rushJsonFolder);
                conditionResult = conditionScriptModule.canShowAlert();
                if (typeof conditionResult !== 'boolean') {
                    throw new Error('canShowAlert() did not return a boolean value');
                }
            }
            catch (e) {
                throw new Error(`Error invoking condition script "${conditionScript}" from rush-alerts.json:\n${e.stack}`);
            }
            finally {
                process.chdir(oldCwd);
            }
            const totalMs = performance.now() - startTimemark;
            this._terminal.writeDebugLine(`Invoked conditionScript "${conditionScript}"` +
                ` in ${Math.round(totalMs)} ms with result "${conditionResult}"`);
            if (!conditionResult) {
                return false;
            }
        }
        return true;
    }
    _printMessageInBoxStyle(alert) {
        var _a;
        const boxTitle = alert.title.toUpperCase();
        const boxMessage = typeof alert.message === 'string' ? alert.message : alert.message.join('');
        const boxDetails = alert.detailsUrl ? 'Details: ' + alert.detailsUrl : '';
        // ...minus the padding.
        const PADDING = '╔══╗'.length;
        // Try to make it wide enough to fit the (unwrapped) strings...
        let lineLength = Math.max(boxTitle.length, boxMessage.length, boxDetails.length);
        // ...but don't exceed the console width, and also keep it under 80...
        lineLength = Math.min(lineLength, ((_a = terminal_1.PrintUtilities.getConsoleWidth()) !== null && _a !== void 0 ? _a : 80) - PADDING, 80 - PADDING);
        // ...and the width needs to be at least 40 characters...
        lineLength = Math.max(lineLength, 40 - PADDING);
        const lines = [
            ...terminal_1.PrintUtilities.wrapWordsToLines(boxTitle, lineLength).map((x) => terminal_1.Colorize.bold(x.padEnd(lineLength))),
            '',
            ...terminal_1.PrintUtilities.wrapWordsToLines(boxMessage, lineLength).map((x) => x.padEnd(lineLength))
        ];
        if (boxDetails) {
            lines.push('', ...terminal_1.PrintUtilities.wrapWordsToLines(boxDetails, lineLength).map((x) => terminal_1.Colorize.cyan(x.padEnd(lineLength))));
        }
        // Print the box
        this._terminal.writeLine('╔═' + '═'.repeat(lineLength) + '═╗');
        for (const line of lines) {
            this._terminal.writeLine(`║ ${line.padEnd(lineLength)} ║`);
        }
        this._terminal.writeLine('╚═' + '═'.repeat(lineLength) + '═╝');
        this._terminal.writeLine(`To stop seeing this alert, run "rush alert --snooze ${alert.alertId}"`);
    }
    async _writeRushAlertStateAsync() {
        await node_core_library_1.JsonFile.saveAsync(this._rushAlertsState, this.rushAlertsStateFilePath, {
            ignoreUndefinedValues: true,
            headerComment: '// THIS FILE IS MACHINE-GENERATED -- DO NOT MODIFY',
            jsonSyntax: node_core_library_1.JsonSyntax.JsonWithComments
        });
    }
}
exports.RushAlerts = RushAlerts;
RushAlerts.ALERT_PRIORITY = [
    AlertPriority.HIGH,
    AlertPriority.NORMAL,
    AlertPriority.LOW
];
RushAlerts.alertDisplayIntervalDurations = new Map([
    [AlertDisplayInterval.ALWAYS, -1],
    [AlertDisplayInterval.MONTHLY, 1000 * 60 * 60 * 24 * 30],
    [AlertDisplayInterval.WEEKLY, 1000 * 60 * 60 * 24 * 7],
    [AlertDisplayInterval.DAILY, 1000 * 60 * 60 * 24],
    [AlertDisplayInterval.HOURLY, 1000 * 60 * 60]
]);
// only display alerts when certain specific actions are triggered
RushAlerts.alertTriggerActions = [
    // TODO: put the rest of the action names in constants
    'add',
    'change',
    'deploy',
    'init',
    'publish',
    actionNameConstants_1.PURGE_ACTION_NAME,
    'remove',
    'update',
    'install',
    'build',
    'list',
    'version'
];
//# sourceMappingURL=RushAlerts.js.map