import { __awaiter, __extends, __generator } from "tslib";
import { groupBy } from '../../lib/group-by';
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted';
import { isOnline } from '../connection';
import { Context, ContextCancelation } from '../context';
import { Emitter } from '../emitter';
import { attempt, ensure } from './delivery';
var EventQueue = /** @class */ (function (_super) {
    __extends(EventQueue, _super);
    function EventQueue(priorityQueue) {
        var _this = _super.call(this) || this;
        _this.plugins = [];
        _this.failedInitializations = [];
        _this.flushing = false;
        _this.queue = priorityQueue !== null && priorityQueue !== void 0 ? priorityQueue : new PersistedPriorityQueue(4, 'event-queue');
        _this.scheduleFlush();
        return _this;
    }
    EventQueue.prototype.register = function (ctx, plugin, instance) {
        return __awaiter(this, void 0, void 0, function () {
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, Promise.resolve(plugin.load(ctx, instance))
                            .then(function () {
                            _this.plugins.push(plugin);
                        })
                            .catch(function (err) {
                            if (plugin.type === 'destination') {
                                _this.failedInitializations.push(plugin.name);
                                console.warn(plugin.name, err);
                                ctx.log('warn', 'Failed to load destination', {
                                    plugin: plugin.name,
                                    error: err,
                                });
                                return;
                            }
                            throw err;
                        })];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    EventQueue.prototype.deregister = function (ctx, plugin, instance) {
        return __awaiter(this, void 0, void 0, function () {
            var e_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 3, , 4]);
                        if (!plugin.unload) return [3 /*break*/, 2];
                        return [4 /*yield*/, Promise.resolve(plugin.unload(ctx, instance))];
                    case 1:
                        _a.sent();
                        _a.label = 2;
                    case 2:
                        this.plugins = this.plugins.filter(function (p) { return p.name !== plugin.name; });
                        return [3 /*break*/, 4];
                    case 3:
                        e_1 = _a.sent();
                        ctx.log('warn', 'Failed to unload destination', {
                            plugin: plugin.name,
                            error: e_1,
                        });
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    EventQueue.prototype.dispatch = function (ctx) {
        return __awaiter(this, void 0, void 0, function () {
            var willDeliver;
            return __generator(this, function (_a) {
                ctx.log('debug', 'Dispatching');
                ctx.stats.increment('message_dispatched');
                this.queue.push(ctx);
                willDeliver = this.subscribeToDelivery(ctx);
                this.scheduleFlush(0);
                return [2 /*return*/, willDeliver];
            });
        });
    };
    EventQueue.prototype.subscribeToDelivery = function (ctx) {
        return __awaiter(this, void 0, void 0, function () {
            var _this = this;
            return __generator(this, function (_a) {
                return [2 /*return*/, new Promise(function (resolve, reject) {
                        var onDeliver = function (flushed, delivered) {
                            if (flushed.isSame(ctx)) {
                                _this.off('flush', onDeliver);
                                if (delivered) {
                                    resolve(flushed);
                                }
                                else {
                                    reject(flushed);
                                }
                            }
                        };
                        _this.on('flush', onDeliver);
                    })];
            });
        });
    };
    EventQueue.prototype.dispatchSingle = function (ctx) {
        return __awaiter(this, void 0, void 0, function () {
            var _this = this;
            return __generator(this, function (_a) {
                ctx.log('debug', 'Dispatching');
                ctx.stats.increment('message_dispatched');
                this.queue.updateAttempts(ctx);
                ctx.attempts = 1;
                return [2 /*return*/, this.deliver(ctx).catch(function (err) {
                        if (err instanceof ContextCancelation && err.retry === false) {
                            return ctx;
                        }
                        var accepted = _this.enqueuRetry(err, ctx);
                        if (!accepted) {
                            throw err;
                        }
                        return _this.subscribeToDelivery(ctx);
                    })];
            });
        });
    };
    EventQueue.prototype.isEmpty = function () {
        return this.queue.length === 0;
    };
    EventQueue.prototype.scheduleFlush = function (timeout) {
        var _this = this;
        if (timeout === void 0) { timeout = 500; }
        if (this.flushing) {
            return;
        }
        this.flushing = true;
        setTimeout(function () {
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
            _this.flush().then(function () {
                setTimeout(function () {
                    _this.flushing = false;
                    if (_this.queue.length) {
                        _this.scheduleFlush(0);
                    }
                    else {
                        _this.scheduleFlush(500);
                    }
                }, 0);
            });
        }, timeout);
    };
    EventQueue.prototype.deliver = function (ctx) {
        return __awaiter(this, void 0, void 0, function () {
            var start, done, err_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        start = Date.now();
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, this.flushOne(ctx)];
                    case 2:
                        ctx = _a.sent();
                        done = Date.now() - start;
                        ctx.stats.gauge('delivered', done);
                        ctx.log('debug', 'Delivered', ctx.event);
                        return [2 /*return*/, ctx];
                    case 3:
                        err_1 = _a.sent();
                        ctx.log('error', 'Failed to deliver', err_1);
                        ctx.stats.increment('delivery_failed');
                        throw err_1;
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    EventQueue.prototype.enqueuRetry = function (err, ctx) {
        var notRetriable = err instanceof ContextCancelation && err.retry === false;
        var retriable = !notRetriable;
        if (retriable) {
            var accepted = this.queue.pushWithBackoff(ctx);
            return accepted;
        }
        return false;
    };
    EventQueue.prototype.flush = function () {
        return __awaiter(this, void 0, void 0, function () {
            var ctx, err_2, accepted;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (this.queue.length === 0 || !isOnline()) {
                            return [2 /*return*/, []];
                        }
                        ctx = this.queue.pop();
                        if (!ctx) {
                            return [2 /*return*/, []];
                        }
                        ctx.attempts = this.queue.getAttempts(ctx);
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        return [4 /*yield*/, this.deliver(ctx)];
                    case 2:
                        ctx = _a.sent();
                        this.emit('flush', ctx, true);
                        return [3 /*break*/, 4];
                    case 3:
                        err_2 = _a.sent();
                        accepted = this.enqueuRetry(err_2, ctx);
                        if (!accepted) {
                            this.emit('flush', ctx, false);
                        }
                        return [2 /*return*/, []];
                    case 4: return [2 /*return*/, [ctx]];
                }
            });
        });
    };
    EventQueue.prototype.isReady = function () {
        // return this.plugins.every((p) => p.isLoaded())
        // should we wait for every plugin to load?
        return true;
    };
    EventQueue.prototype.availableExtensions = function (denyList) {
        var available = denyList.All === false
            ? this.plugins.filter(function (p) { return denyList[p.name] === true || p.type !== 'destination'; })
            : // !== false includes plugins not present on the denyList
                this.plugins.filter(function (p) { return denyList[p.name] !== false; });
        var _a = groupBy(available, 'type'), _b = _a.before, before = _b === void 0 ? [] : _b, _c = _a.enrichment, enrichment = _c === void 0 ? [] : _c, _d = _a.destination, destination = _d === void 0 ? [] : _d, _e = _a.after, after = _e === void 0 ? [] : _e;
        return {
            before: before,
            enrichment: enrichment,
            destinations: destination,
            after: after,
        };
    };
    EventQueue.prototype.flushOne = function (ctx) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function () {
            var _c, before, enrichment, _i, before_1, beforeWare, temp, _d, enrichment_1, enrichmentWare, temp, _e, destinations, after, afterCalls;
            return __generator(this, function (_f) {
                switch (_f.label) {
                    case 0:
                        if (!this.isReady()) {
                            throw new Error('Not ready');
                        }
                        _c = this.availableExtensions((_a = ctx.event.integrations) !== null && _a !== void 0 ? _a : {}), before = _c.before, enrichment = _c.enrichment;
                        _i = 0, before_1 = before;
                        _f.label = 1;
                    case 1:
                        if (!(_i < before_1.length)) return [3 /*break*/, 4];
                        beforeWare = before_1[_i];
                        return [4 /*yield*/, ensure(ctx, beforeWare)];
                    case 2:
                        temp = _f.sent();
                        if (temp !== undefined) {
                            ctx = temp;
                        }
                        _f.label = 3;
                    case 3:
                        _i++;
                        return [3 /*break*/, 1];
                    case 4:
                        _d = 0, enrichment_1 = enrichment;
                        _f.label = 5;
                    case 5:
                        if (!(_d < enrichment_1.length)) return [3 /*break*/, 8];
                        enrichmentWare = enrichment_1[_d];
                        return [4 /*yield*/, attempt(ctx, enrichmentWare)];
                    case 6:
                        temp = _f.sent();
                        if (temp instanceof Context) {
                            ctx = temp;
                        }
                        _f.label = 7;
                    case 7:
                        _d++;
                        return [3 /*break*/, 5];
                    case 8:
                        _e = this.availableExtensions((_b = ctx.event.integrations) !== null && _b !== void 0 ? _b : {}), destinations = _e.destinations, after = _e.after;
                        return [4 /*yield*/, new Promise(function (resolve, reject) {
                                setTimeout(function () {
                                    var attempts = destinations.map(function (destination) {
                                        return attempt(ctx, destination);
                                    });
                                    Promise.all(attempts).then(resolve).catch(reject);
                                }, 0);
                            })];
                    case 9:
                        _f.sent();
                        ctx.stats.increment('message_delivered');
                        afterCalls = after.map(function (after) { return attempt(ctx, after); });
                        return [4 /*yield*/, Promise.all(afterCalls)];
                    case 10:
                        _f.sent();
                        return [2 /*return*/, ctx];
                }
            });
        });
    };
    return EventQueue;
}(Emitter));
export { EventQueue };
//# sourceMappingURL=event-queue.js.map