"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractReconnectStateManager = void 0;
const p_limit_1 = __importDefault(require("p-limit"));
const stream_connection_interface_1 = require("../delivery/stream/stream-connection.interface");
const logger_1 = require("../logger");
const detect_environment_1 = require("../util/detect-environment");
const time_1 = require("../util/time");
const connection_error_1 = require("./connection-error");
const recover_error_1 = require("./recover-error");
const state_manager_1 = require("./state-manager");
const state_manager_interface_1 = require("./state-manager.interface");
class AbstractReconnectStateManager extends state_manager_1.AbstractStateManager {
    constructor() {
        super(...arguments);
        this.log = logger_1.logger.getLogger('reconnect-manager');
        this.environment = (0, detect_environment_1.detectEnvironment)();
        this.reconnectLimit = (0, p_limit_1.default)(1);
        this.firstInitFailed = false;
        this.firstInitSuccess = false;
        this.reconnectFailed = false;
    }
    async open() {
        await this.reconnectLimit(async () => {
            if (!this.firstInitSuccess) {
                await this.openFirstTime();
            }
            if (this.state === state_manager_interface_1.State.Closed) {
                await this.reconnect();
            }
        });
        return this;
    }
    async close() {
        if (this.reconnectTimeout) {
            clearTimeout(this.reconnectTimeout);
        }
        if (this.onCleanUp) {
            this.onCleanUp();
        }
        // To avoid situation, when cleanUp is not able to call an appropriate onCleanUp()
        this.reconnectFailed = true;
        return this.reconnectLimit(async () => {
            this.firstInitFailed = false;
            this.firstInitSuccess = false;
            this.reconnectFailed = false;
            await super.close();
        });
    }
    async closeWithKeptReconnect() {
        return super.close();
    }
    async reopen() {
        const intervals = [...stream_connection_interface_1.StreamReconnectIntervals[this.environment]];
        await this.recover(intervals);
    }
    async openFirstTime() {
        if (this.firstInitFailed) {
            throw new connection_error_1.ConnectionError('The first initialization failed!');
        }
        try {
            await super.open();
            this.firstInitSuccess = true;
        }
        catch (err) {
            this.firstInitFailed = true;
            throw err;
        }
    }
    async reconnect() {
        if (this.reconnectFailed) {
            this.log.error('Recover has failed!');
            throw new recover_error_1.RecoverError('Recover has failed!');
        }
        try {
            await this.reopen();
            this.log.warn('Reopened successfully!');
        }
        catch (err) {
            this.reconnectFailed = true;
            this.log.error('Reconnect error', err);
            throw err;
        }
    }
    async recover(intervals) {
        this.log.debug(`Start recovering. Intervals:`, intervals);
        return new Promise(async (resolve, reject) => {
            this.onCleanUp = reject.bind(null, new Error('Initialization dropped due to close() action'));
            try {
                await super.open();
                resolve(void 0);
            }
            catch (err) {
                if (intervals.length === 0) {
                    reject(err);
                    return;
                }
                this.reconnectTimeout = setTimeout(() => {
                    // For Node.js it will be constant retry for the last interval until the session is closed
                    if (!(intervals.length === 1 && this.environment === "NODEJS" /* NODEJS */)) {
                        intervals.shift();
                    }
                    resolve(this.recover(intervals));
                }, intervals[0] * 1000);
                this.log.info(`Next reconnection attempt is in ${(0, time_1.formatTime)(intervals[0])}`);
            }
        });
    }
}
exports.AbstractReconnectStateManager = AbstractReconnectStateManager;
//# sourceMappingURL=data:application/json;base64, 