"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Unleash = exports.UnleashEvents = exports.Strategy = void 0;
const os_1 = require("os");
const events_1 = require("events");
const client_1 = require("./client");
const repository_1 = require("./repository");
const metrics_1 = require("./metrics");
const strategy_1 = require("./strategy");
Object.defineProperty(exports, "Strategy", { enumerable: true, get: function () { return strategy_1.Strategy; } });
const variant_1 = require("./variant");
const helpers_1 = require("./helpers");
const bootstrap_provider_1 = require("./repository/bootstrap-provider");
const events_2 = require("./events");
Object.defineProperty(exports, "UnleashEvents", { enumerable: true, get: function () { return events_2.UnleashEvents; } });
const storage_provider_file_1 = require("./repository/storage-provider-file");
const uuidv4_1 = require("./uuidv4");
const metric_types_1 = require("./impact-metrics/metric-types");
const BACKUP_PATH = (0, os_1.tmpdir)();
class Unleash extends events_1.EventEmitter {
    constructor({ appName, environment = 'default', projectName, instanceId, url, refreshInterval = 15 * 1000, metricsInterval = 60 * 1000, metricsJitter = 0, disableMetrics = false, backupPath = BACKUP_PATH, strategies = [], repository, namePrefix, customHeaders, customHeadersFunction, timeout, httpOptions, tags, bootstrap = {}, bootstrapOverride, storageProvider, disableAutoStart = false, skipInstanceCountWarning = false, experimentalMode = { type: 'polling', format: 'full' }, }) {
        super();
        this.synchronized = false;
        this.ready = false;
        this.started = false;
        this.metricRegistry = new metric_types_1.InMemoryMetricRegistry();
        Unleash.instanceCount++;
        this.on(events_2.UnleashEvents.Error, (error) => {
            // Only if there does not exist other listeners for this event.
            if (this.listenerCount(events_2.UnleashEvents.Error) === 1) {
                console.error(error);
            }
        });
        if (!skipInstanceCountWarning && Unleash.instanceCount > 10) {
            process.nextTick(() => {
                const error = new Error('The unleash SDK has been initialized more than 10 times');
                this.emit(events_2.UnleashEvents.Error, error);
            });
        }
        if (!url) {
            throw new Error('Unleash API "url" is required');
        }
        if (!appName) {
            throw new Error('Unleash client "appName" is required');
        }
        const unleashUrl = this.cleanUnleashUrl(url);
        const unleashInstanceId = (0, helpers_1.generateInstanceId)(instanceId);
        const unleashConnectionId = (0, uuidv4_1.uuidv4)();
        this.staticContext = { appName, environment };
        const bootstrapProvider = (0, bootstrap_provider_1.resolveBootstrapProvider)(bootstrap, appName, unleashInstanceId);
        this.repository =
            repository ||
                new repository_1.default({
                    projectName,
                    url: unleashUrl,
                    appName,
                    instanceId: unleashInstanceId,
                    connectionId: unleashConnectionId,
                    refreshInterval,
                    headers: customHeaders,
                    customHeadersFunction,
                    timeout,
                    httpOptions,
                    namePrefix,
                    tags,
                    bootstrapProvider,
                    bootstrapOverride,
                    mode: experimentalMode,
                    storageProvider: storageProvider || new storage_provider_file_1.default(backupPath),
                });
        this.repository.on(events_2.UnleashEvents.Ready, () => {
            this.ready = true;
            process.nextTick(() => {
                this.emit(events_2.UnleashEvents.Ready);
            });
        });
        this.repository.on(events_2.UnleashEvents.Error, (err) => {
            // eslint-disable-next-line no-param-reassign
            err.message = `Unleash Repository error: ${err.message}`;
            this.emit(events_2.UnleashEvents.Error, err);
        });
        this.repository.on(events_2.UnleashEvents.Warn, (msg) => this.emit(events_2.UnleashEvents.Warn, msg));
        this.repository.on(events_2.UnleashEvents.Unchanged, (msg) => this.emit(events_2.UnleashEvents.Unchanged, msg));
        this.repository.on(events_2.UnleashEvents.Changed, (data) => {
            this.emit(events_2.UnleashEvents.Changed, data);
            // Only emit the fully synchronized event the first time.
            if (!this.synchronized) {
                this.synchronized = true;
                process.nextTick(() => this.emit(events_2.UnleashEvents.Synchronized));
            }
        });
        // setup client
        const supportedStrategies = strategies.concat(strategy_1.defaultStrategies);
        this.client = new client_1.default(this.repository, supportedStrategies);
        this.client.on(events_2.UnleashEvents.Error, (err) => this.emit(events_2.UnleashEvents.Error, err));
        this.client.on(events_2.UnleashEvents.Impression, (e) => this.emit(events_2.UnleashEvents.Impression, e));
        this.metrics = new metrics_1.default({
            disableMetrics,
            appName,
            instanceId: unleashInstanceId,
            connectionId: unleashConnectionId,
            strategies: supportedStrategies.map((strategy) => strategy.name),
            metricsInterval,
            metricsJitter,
            url: unleashUrl,
            headers: customHeaders,
            customHeadersFunction,
            timeout,
            httpOptions,
            metricRegistry: this.metricRegistry,
        });
        this.metrics.on(events_2.UnleashEvents.Error, (err) => {
            // eslint-disable-next-line no-param-reassign
            err.message = `Unleash Metrics error: ${err.message}`;
            this.emit(events_2.UnleashEvents.Error, err);
        });
        this.metrics.on(events_2.UnleashEvents.Warn, (msg) => this.emit(events_2.UnleashEvents.Warn, msg));
        this.metrics.on(events_2.UnleashEvents.Sent, (payload) => this.emit(events_2.UnleashEvents.Sent, payload));
        this.metrics.on(events_2.UnleashEvents.Count, (name, enabled) => {
            this.emit(events_2.UnleashEvents.Count, name, enabled);
        });
        this.metrics.on(events_2.UnleashEvents.Registered, (payload) => {
            this.emit(events_2.UnleashEvents.Registered, payload);
        });
        if (!disableAutoStart) {
            process.nextTick(async () => this.start());
        }
    }
    /**
     * Will only give you an instance the first time you call the method,
     * and then return the same instance.
     * @param config The Unleash Config.
     * @returns the Unleash instance
     */
    static getInstance(config) {
        const cleanConfig = {
            ...config,
            // Remove complex objects
            repository: undefined,
            customHeadersFunction: undefined,
            storageProvider: undefined,
        };
        const configSignature = (0, helpers_1.generateHashOfConfig)(cleanConfig);
        if (Unleash.instance) {
            if (configSignature !== Unleash.configSignature) {
                throw new Error('You already have an Unleash instance with a different configuration.');
            }
            return Unleash.instance;
        }
        const instance = new Unleash(config);
        Unleash.instance = instance;
        Unleash.configSignature = configSignature;
        return instance;
    }
    cleanUnleashUrl(url) {
        let unleashUrl = url;
        if (unleashUrl.endsWith('/features')) {
            const oldUrl = unleashUrl;
            process.nextTick(() => this.emit(events_2.UnleashEvents.Warn, `Unleash server URL "${oldUrl}" should no longer link directly to /features`));
            unleashUrl = unleashUrl.replace(/\/features$/, '');
        }
        if (!unleashUrl.endsWith('/')) {
            unleashUrl += '/';
        }
        return unleashUrl;
    }
    isSynchronized() {
        return this.synchronized;
    }
    async start() {
        if (this.started)
            return;
        this.started = true;
        await Promise.all([this.repository.start(), this.metrics.start()]);
    }
    destroy() {
        this.repository.stop();
        this.metrics.stop();
        Unleash.instance = undefined;
        Unleash.configSignature = undefined;
        Unleash.instanceCount--;
    }
    isEnabled(name, context = {}, fallback) {
        const enhancedContext = { ...this.staticContext, ...context };
        const fallbackFunc = (0, helpers_1.createFallbackFunction)(name, enhancedContext, fallback);
        let result;
        if (this.ready) {
            result = this.client.isEnabled(name, enhancedContext, fallbackFunc);
        }
        else {
            result = fallbackFunc();
            this.emit(events_2.UnleashEvents.Warn, `Unleash has not been initialized yet. isEnabled(${name}) defaulted to ${result}`);
        }
        this.count(name, result);
        return result;
    }
    getVariant(name, context = {}, fallbackVariant) {
        const enhancedContext = { ...this.staticContext, ...context };
        let variant;
        if (this.ready) {
            variant = this.client.getVariant(name, enhancedContext, fallbackVariant);
        }
        else {
            variant =
                typeof fallbackVariant !== 'undefined'
                    ? { ...fallbackVariant, feature_enabled: false, featureEnabled: false }
                    : { ...variant_1.defaultVariant, featureEnabled: variant_1.defaultVariant.feature_enabled };
            this.emit(events_2.UnleashEvents.Warn, `Unleash has not been initialized yet. isEnabled(${name}) defaulted to ${variant}`);
        }
        if (variant.name) {
            this.countVariant(name, variant.name);
        }
        this.count(name, Boolean(variant.feature_enabled));
        return variant;
    }
    forceGetVariant(name, context = {}, fallbackVariant) {
        const enhancedContext = { ...this.staticContext, ...context };
        let variant;
        if (this.ready) {
            variant = this.client.forceGetVariant(name, enhancedContext, fallbackVariant);
        }
        else {
            variant =
                typeof fallbackVariant !== 'undefined'
                    ? { ...fallbackVariant, feature_enabled: false }
                    : variant_1.defaultVariant;
            this.emit(events_2.UnleashEvents.Warn, `Unleash has not been initialized yet. isEnabled(${name}) defaulted to ${variant}`);
        }
        if (variant.name) {
            this.countVariant(name, variant.name);
        }
        this.count(name, variant.feature_enabled || false);
        return variant;
    }
    getFeatureToggleDefinition(toggleName) {
        return this.repository.getToggle(toggleName);
    }
    getFeatureToggleDefinitions(withFullSegments) {
        if (withFullSegments === true) {
            return this.repository.getTogglesWithSegmentData();
        }
        return this.repository.getToggles();
    }
    count(toggleName, enabled) {
        this.metrics.count(toggleName, enabled);
    }
    countVariant(toggleName, variantName) {
        this.metrics.countVariant(toggleName, variantName);
    }
    flushMetrics() {
        return this.metrics.sendMetrics();
    }
    async destroyWithFlush() {
        await this.flushMetrics();
        this.destroy();
    }
    /**
     * Experimental: Change the fetching mode at runtime
     * @param mode The new mode to switch to ('polling' or 'streaming')
     */
    async setExperimentalMode(mode) {
        if (this.repository.setMode) {
            return this.repository.setMode(mode);
        }
        throw new Error('setMode is not supported by this repository implementation');
    }
}
exports.Unleash = Unleash;
Unleash.instanceCount = 0;//# sourceMappingURL=https://main.vscode-cdn.net/sourcemaps/e0b7a2bcf575cfba10528c4e7c10bd3ce2d7769a/node_modules/unleash-client/lib/unleash.js.map