"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnalyticsService = void 0;
const path = require("path");
const _ = require("lodash");
const decorators_1 = require("../../common/decorators");
const helpers_1 = require("../../common/helpers");
const constants_1 = require("../../common/constants");
const yok_1 = require("../../common/yok");
class AnalyticsService {
    constructor($logger, $options, $staticConfig, $prompter, $userSettingsService, $analyticsSettingsService, $childProcess, $projectDataService, $mobileHelper, $projectHelper) {
        this.$logger = $logger;
        this.$options = $options;
        this.$staticConfig = $staticConfig;
        this.$prompter = $prompter;
        this.$userSettingsService = $userSettingsService;
        this.$analyticsSettingsService = $analyticsSettingsService;
        this.$childProcess = $childProcess;
        this.$projectDataService = $projectDataService;
        this.$mobileHelper = $mobileHelper;
        this.$projectHelper = $projectHelper;
        this.shouldDisposeInstance = true;
        this.analyticsStatuses = {};
    }
    setShouldDispose(shouldDispose) {
        this.shouldDisposeInstance = shouldDispose;
    }
    async checkConsent() {
        if (await this.$analyticsSettingsService.canDoRequest()) {
            const initialTrackFeatureUsageStatus = await this.getStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME);
            let trackFeatureUsage = initialTrackFeatureUsageStatus === "enabled";
            if ((await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME)) &&
                (0, helpers_1.isInteractive)()) {
                const message = `Do you want to help us improve ${this.$analyticsSettingsService.getClientName()} by automatically sending anonymous usage statistics? We will not use this information to identify or contact you.`;
                trackFeatureUsage = await this.$prompter.confirm(message, () => true);
                await this.setStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, trackFeatureUsage);
                await this.trackAcceptFeatureUsage({
                    acceptTrackFeatureUsage: trackFeatureUsage,
                });
            }
            const isErrorReportingUnset = await this.isNotConfirmed(this.$staticConfig.ERROR_REPORT_SETTING_NAME);
            const isUsageReportingConfirmed = !(await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME));
            if (isErrorReportingUnset && isUsageReportingConfirmed) {
                await this.setStatus(this.$staticConfig.ERROR_REPORT_SETTING_NAME, trackFeatureUsage);
            }
        }
    }
    async setStatus(settingName, enabled) {
        this.analyticsStatuses[settingName] = enabled
            ? "enabled"
            : "disabled";
        await this.$userSettingsService.saveSetting(settingName, enabled.toString());
    }
    async isEnabled(settingName) {
        const analyticsStatus = await this.getStatus(settingName);
        return analyticsStatus === "enabled";
    }
    getStatusMessage(settingName, jsonFormat, readableSettingName) {
        if (jsonFormat) {
            return this.getJsonStatusMessage(settingName);
        }
        return this.getHumanReadableStatusMessage(settingName, readableSettingName);
    }
    async trackAcceptFeatureUsage(settings) {
        const acceptTracking = !!(settings && settings.acceptTrackFeatureUsage);
        const googleAnalyticsEventData = {
            googleAnalyticsDataType: "event",
            action: "Accept Tracking",
            label: acceptTracking.toString(),
        };
        await this.forcefullyTrackInGoogleAnalytics(googleAnalyticsEventData);
    }
    async trackInGoogleAnalytics(gaSettings) {
        await this.initAnalyticsStatuses();
        if (!this.$staticConfig.disableAnalytics &&
            this.analyticsStatuses[this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME] === "enabled") {
            return this.forcefullyTrackInGoogleAnalytics(gaSettings);
        }
    }
    async trackEventActionInGoogleAnalytics(data) {
        const device = data.device;
        const platform = device ? device.deviceInfo.platform : data.platform;
        const normalizedPlatform = platform
            ? this.$mobileHelper.normalizePlatformName(platform)
            : platform;
        const isForDevice = device ? !device.isEmulator : data.isForDevice;
        let label = "";
        label = this.addDataToLabel(label, normalizedPlatform);
        if (isForDevice !== null && isForDevice !== undefined) {
            const deviceType = isForDevice
                ? constants_1.DeviceTypes.Device
                : this.$mobileHelper.isAndroidPlatform(platform)
                    ? constants_1.DeviceTypes.Emulator
                    : constants_1.DeviceTypes.Simulator;
            label = this.addDataToLabel(label, deviceType);
        }
        if (device) {
            label = this.addDataToLabel(label, device.deviceInfo.version);
        }
        if (data.additionalData) {
            label = this.addDataToLabel(label, data.additionalData);
        }
        const customDimensions = {};
        this.setProjectRelatedCustomDimensions(customDimensions, data.projectDir);
        const googleAnalyticsEventData = {
            googleAnalyticsDataType: "event",
            action: data.action,
            label,
            customDimensions,
            value: data.value,
        };
        await this.trackInGoogleAnalytics(googleAnalyticsEventData);
    }
    async finishTracking() {
        return new Promise((resolve, reject) => {
            if (this.brokerProcess && this.brokerProcess.connected) {
                let timer;
                const handler = (data) => {
                    if (data === "ProcessFinishedTasks") {
                        this.brokerProcess.removeListener("message", handler);
                        clearTimeout(timer);
                        resolve();
                    }
                };
                timer = setTimeout(() => {
                    this.brokerProcess.removeListener("message", handler);
                    resolve();
                }, 3000);
                this.brokerProcess.on("message", handler);
                const msg = { type: "FinishTracking" };
                this.brokerProcess.send(msg, (err) => this.$logger.trace(`Error while sending ${JSON.stringify(msg)}`));
            }
            else {
                resolve();
            }
        });
    }
    forcefullyTrackInGoogleAnalytics(gaSettings) {
        gaSettings.customDimensions = gaSettings.customDimensions || {};
        gaSettings.customDimensions["cd5"] =
            this.$options.analyticsClient ||
                ((0, helpers_1.isInteractive)() ? "CLI" : "Unknown");
        this.setProjectRelatedCustomDimensions(gaSettings.customDimensions);
        const googleAnalyticsData = _.merge({
            type: "googleAnalyticsData",
            category: "CLI",
        }, gaSettings);
        this.$logger.trace("Will send the following information to Google Analytics:", googleAnalyticsData);
        return this.sendMessageToBroker(googleAnalyticsData);
    }
    setProjectRelatedCustomDimensions(customDimensions, projectDir) {
        if (!projectDir) {
            try {
                projectDir = this.$projectHelper.projectDir;
            }
            catch (err) {
                this.$logger.trace("Unable to get the projectDir from projectHelper", err);
            }
        }
        if (projectDir) {
            const projectData = this.$projectDataService.getProjectData(projectDir);
            customDimensions["cd2"] =
                projectData.projectType;
            customDimensions["cd9"] =
                projectData.isShared.toString();
        }
        return customDimensions;
    }
    dispose() {
        if (this.brokerProcess && this.shouldDisposeInstance) {
            this.brokerProcess.disconnect();
        }
    }
    addDataToLabel(label, newData) {
        if (newData && label) {
            return `${label}_${newData}`;
        }
        return label || newData || "";
    }
    getAnalyticsBroker() {
        return new Promise((resolve, reject) => {
            const brokerProcessArgs = this.getBrokerProcessArgs();
            const broker = this.$childProcess.spawn(process.execPath, brokerProcessArgs, {
                stdio: ["ignore", "ignore", "ignore", "ipc"],
                detached: true,
            });
            broker.unref();
            let isSettled = false;
            const timeoutId = setTimeout(() => {
                if (!isSettled) {
                    reject(new Error("Unable to start Analytics Broker process."));
                }
            }, AnalyticsService.ANALYTICS_BROKER_START_TIMEOUT);
            broker.on("error", (err) => {
                clearTimeout(timeoutId);
                if (!isSettled) {
                    isSettled = true;
                    reject(err);
                }
            });
            broker.on("message", (data) => {
                if (data === "ProcessReadyToReceive") {
                    clearTimeout(timeoutId);
                    if (!isSettled) {
                        isSettled = true;
                        this.brokerProcess = broker;
                        resolve(broker);
                    }
                }
            });
        });
    }
    getBrokerProcessArgs() {
        const brokerProcessArgs = [
            path.join(__dirname, "analytics-broker-process.js"),
            this.$staticConfig.PATH_TO_BOOTSTRAP,
        ];
        if (this.$options.analyticsLogFile) {
            brokerProcessArgs.push(this.$options.analyticsLogFile);
        }
        return brokerProcessArgs;
    }
    async sendInfoForTracking(trackingInfo, settingName) {
        await this.initAnalyticsStatuses();
        if (!this.$staticConfig.disableAnalytics &&
            this.analyticsStatuses[settingName] === "enabled") {
            return this.sendMessageToBroker(trackingInfo);
        }
    }
    async sendMessageToBroker(message) {
        let broker;
        try {
            broker = await this.getAnalyticsBroker();
        }
        catch (err) {
            this.$logger.trace("Unable to get broker instance due to error: ", err);
            return;
        }
        return new Promise((resolve, reject) => {
            if (broker && broker.connected) {
                try {
                    broker.send(message, (error) => resolve());
                }
                catch (err) {
                    this.$logger.trace("Error while trying to send message to broker:", err);
                    resolve();
                }
            }
            else {
                this.$logger.trace("Broker not found or not connected.");
                resolve();
            }
        });
    }
    async initAnalyticsStatuses() {
        if (await this.$analyticsSettingsService.canDoRequest()) {
            this.$logger.trace("Initializing analytics statuses.");
            const settingsNames = [
                this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME,
                this.$staticConfig.ERROR_REPORT_SETTING_NAME,
            ];
            for (const settingName of settingsNames) {
                await this.getStatus(settingName);
            }
            this.$logger.trace("Analytics statuses: ", this.analyticsStatuses);
        }
    }
    async getStatus(settingName) {
        if (!_.has(this.analyticsStatuses, settingName)) {
            const settingValue = await this.$userSettingsService.getSettingValue(settingName);
            if (settingValue) {
                const isEnabled = (0, helpers_1.toBoolean)(settingValue);
                if (isEnabled) {
                    this.analyticsStatuses[settingName] = "enabled";
                }
                else {
                    this.analyticsStatuses[settingName] = "disabled";
                }
            }
            else {
                this.analyticsStatuses[settingName] = "not confirmed";
            }
        }
        return this.analyticsStatuses[settingName];
    }
    async isNotConfirmed(settingName) {
        const analyticsStatus = await this.getStatus(settingName);
        return analyticsStatus === "not confirmed";
    }
    async getHumanReadableStatusMessage(settingName, readableSettingName) {
        let status = null;
        if (await this.isNotConfirmed(settingName)) {
            status = "disabled until confirmed";
        }
        else {
            status = await this.getStatus(settingName);
        }
        return `${readableSettingName} is ${status}.`;
    }
    async getJsonStatusMessage(settingName) {
        const status = await this.getStatus(settingName);
        const enabled = status === "not confirmed"
            ? null
            : status === "enabled";
        return JSON.stringify({ enabled });
    }
    trackException(exception, message) {
        const data = {
            type: "exception",
            exception,
            message,
        };
        return this.sendInfoForTracking(data, this.$staticConfig.ERROR_REPORT_SETTING_NAME);
    }
}
exports.AnalyticsService = AnalyticsService;
AnalyticsService.ANALYTICS_BROKER_START_TIMEOUT = 10 * 1000;
__decorate([
    (0, decorators_1.cache)()
], AnalyticsService.prototype, "getAnalyticsBroker", null);
__decorate([
    (0, decorators_1.cache)()
], AnalyticsService.prototype, "initAnalyticsStatuses", null);
yok_1.injector.register("analyticsService", AnalyticsService);
