"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const request_1 = require("./request");
const details_json_1 = require("./details.json");
const url_utils_1 = require("./url-utils");
const events_2 = require("./events");
const helpers_1 = require("./helpers");
const repository_1 = require("./repository");
class Metrics extends events_1.EventEmitter {
    constructor({ appName, instanceId, strategies, metricsInterval = 0, metricsJitter = 0, disableMetrics = false, url, headers, customHeadersFunction, timeout, httpOptions, }) {
        super();
        this.failures = 0;
        this.disabled = disableMetrics;
        this.metricsInterval = metricsInterval;
        this.metricsJitter = metricsJitter;
        this.appName = appName;
        this.instanceId = instanceId;
        this.sdkVersion = details_json_1.sdkVersion;
        this.strategies = strategies;
        this.url = url;
        this.headers = headers;
        this.customHeadersFunction = customHeadersFunction;
        this.started = new Date();
        this.timeout = timeout;
        this.bucket = this.createBucket();
        this.httpOptions = httpOptions;
        this.platformData = this.getPlatformData();
    }
    getAppliedJitter() {
        return (0, helpers_1.getAppliedJitter)(this.metricsJitter);
    }
    getFailures() {
        return this.failures;
    }
    getInterval() {
        if (this.metricsInterval === 0) {
            return 0;
        }
        else {
            return this.metricsInterval + this.failures * this.metricsInterval + this.getAppliedJitter();
        }
    }
    startTimer() {
        if (this.disabled || this.getInterval() === 0) {
            return;
        }
        this.timer = setTimeout(() => {
            this.sendMetrics();
        }, this.getInterval());
        if (process.env.NODE_ENV !== 'test' && typeof this.timer.unref === 'function') {
            this.timer.unref();
        }
    }
    start() {
        if (this.metricsInterval > 0) {
            this.startTimer();
            this.registerInstance();
        }
    }
    stop() {
        if (this.timer) {
            clearInterval(this.timer);
            delete this.timer;
        }
        this.disabled = true;
    }
    async registerInstance() {
        if (this.disabled) {
            return false;
        }
        const url = (0, url_utils_1.resolveUrl)((0, url_utils_1.suffixSlash)(this.url), './client/register');
        const payload = this.getClientData();
        const headers = this.customHeadersFunction ? await this.customHeadersFunction() : this.headers;
        try {
            const res = await (0, request_1.post)({
                url,
                json: payload,
                appName: this.appName,
                instanceId: this.instanceId,
                headers,
                timeout: this.timeout,
                httpOptions: this.httpOptions,
            });
            if (!res.ok) {
                // status code outside 200 range
                this.emit(events_2.UnleashEvents.Warn, `${url} returning ${res.status}`, await res.text());
            }
            else {
                this.emit(events_2.UnleashEvents.Registered, payload);
            }
        }
        catch (err) {
            this.emit(events_2.UnleashEvents.Warn, err);
        }
        return true;
    }
    configurationError(url, statusCode) {
        this.emit(events_2.UnleashEvents.Warn, `${url} returning ${statusCode}, stopping metrics`);
        this.metricsInterval = 0;
        this.stop();
    }
    backoff(url, statusCode) {
        this.failures = Math.min(10, this.failures + 1);
        // eslint-disable-next-line max-len
        this.emit(events_2.UnleashEvents.Warn, `${url} returning ${statusCode}. Backing off to ${this.failures} times normal interval`);
        this.startTimer();
    }
    async sendMetrics() {
        if (this.disabled) {
            return;
        }
        if (this.bucketIsEmpty()) {
            this.resetBucket();
            this.startTimer();
            return;
        }
        const url = (0, url_utils_1.resolveUrl)((0, url_utils_1.suffixSlash)(this.url), './client/metrics');
        const payload = this.createMetricsData();
        const headers = this.customHeadersFunction ? await this.customHeadersFunction() : this.headers;
        try {
            const res = await (0, request_1.post)({
                url,
                json: payload,
                appName: this.appName,
                instanceId: this.instanceId,
                headers,
                timeout: this.timeout,
                httpOptions: this.httpOptions,
            });
            if (!res.ok) {
                if (res.status === 403 || res.status == 401) {
                    this.configurationError(url, res.status);
                }
                else if (res.status === 404 ||
                    res.status === 429 ||
                    res.status === 500 ||
                    res.status === 502 ||
                    res.status === 503 ||
                    res.status === 504) {
                    this.backoff(url, res.status);
                }
                this.restoreBucket(payload.bucket);
            }
            else {
                this.emit(events_2.UnleashEvents.Sent, payload);
                this.reduceBackoff();
            }
        }
        catch (err) {
            this.restoreBucket(payload.bucket);
            this.emit(events_2.UnleashEvents.Warn, err);
            this.startTimer();
        }
    }
    reduceBackoff() {
        this.failures = Math.max(0, this.failures - 1);
        this.startTimer();
    }
    assertBucket(name) {
        if (this.disabled) {
            return;
        }
        if (!this.bucket.toggles[name]) {
            this.bucket.toggles[name] = {
                yes: 0,
                no: 0,
                variants: {},
            };
        }
    }
    count(name, enabled) {
        if (this.disabled) {
            return;
        }
        this.increaseCounter(name, enabled, 1);
        this.emit(events_2.UnleashEvents.Count, name, enabled);
    }
    countVariant(name, variantName) {
        if (this.disabled) {
            return;
        }
        this.increaseVariantCounter(name, variantName, 1);
        this.emit(events_2.UnleashEvents.CountVariant, name, variantName);
    }
    increaseCounter(name, enabled, inc = 1) {
        if (inc === 0) {
            return;
        }
        this.assertBucket(name);
        this.bucket.toggles[name][enabled ? 'yes' : 'no'] += inc;
    }
    increaseVariantCounter(name, variantName, inc = 1) {
        this.assertBucket(name);
        if (this.bucket.toggles[name].variants[variantName]) {
            this.bucket.toggles[name].variants[variantName] += inc;
        }
        else {
            this.bucket.toggles[name].variants[variantName] = inc;
        }
    }
    bucketIsEmpty() {
        return Object.keys(this.bucket.toggles).length === 0;
    }
    createBucket() {
        return {
            start: new Date(),
            stop: undefined,
            toggles: {},
        };
    }
    resetBucket() {
        this.bucket = this.createBucket();
    }
    createMetricsData() {
        const bucket = { ...this.bucket, stop: new Date() };
        this.resetBucket();
        return {
            appName: this.appName,
            instanceId: this.instanceId,
            bucket,
            platformName: this.platformData.name,
            platformVersion: this.platformData.version,
            yggdrasilVersion: null,
            specVersion: repository_1.SUPPORTED_SPEC_VERSION,
        };
    }
    restoreBucket(bucket) {
        if (this.disabled) {
            return;
        }
        this.bucket.start = bucket.start;
        const { toggles } = bucket;
        Object.keys(toggles).forEach((toggleName) => {
            const toggle = toggles[toggleName];
            this.increaseCounter(toggleName, true, toggle.yes);
            this.increaseCounter(toggleName, false, toggle.no);
            Object.keys(toggle.variants).forEach((variant) => {
                this.increaseVariantCounter(toggleName, variant, toggle.variants[variant]);
            });
        });
    }
    getClientData() {
        return {
            appName: this.appName,
            instanceId: this.instanceId,
            sdkVersion: this.sdkVersion,
            strategies: this.strategies,
            started: this.started,
            interval: this.metricsInterval,
            platformName: this.platformData.name,
            platformVersion: this.platformData.version,
            yggdrasilVersion: null,
            specVersion: repository_1.SUPPORTED_SPEC_VERSION,
        };
    }
    getPlatformData() {
        if (typeof Bun !== 'undefined') {
            return { name: 'bun', version: Bun.version };
        }
        else if (typeof Deno !== 'undefined') {
            return { name: 'deno', version: Deno.version.deno };
        }
        else if (typeof process !== 'undefined' && process.versions && process.versions.node) {
            return { name: 'node', version: process.versions.node };
        }
        else {
            return { name: 'unknown', version: 'unknown' };
        }
    }
}
exports.default = Metrics;//# sourceMappingURL=https://main.vscode-cdn.net/sourcemaps/bfcd46e04becbf1670511e8c1e9cb0f1c1d62983/node_modules/unleash-client/lib/metrics.js.map