"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEventsManager = void 0;
var tslib_1 = require("tslib");
var constants_1 = require("../constants");
var helpers_1 = require("../helpers");
var track_destination_1 = require("../track-destination");
var events_idb_store_1 = require("./events-idb-store");
var events_memory_store_1 = require("./events-memory-store");
var createEventsManager = function (_a) {
    var config = _a.config, minInterval = _a.minInterval, maxInterval = _a.maxInterval, maxPersistedEventsSize = _a.maxPersistedEventsSize, type = _a.type, payloadBatcher = _a.payloadBatcher, storeType = _a.storeType, trackDestinationWorkerScript = _a.trackDestinationWorkerScript, shouldSend = _a.shouldSend;
    return tslib_1.__awaiter(void 0, void 0, void 0, function () {
        function flush(useRetry) {
            if (useRetry === void 0) { useRetry = false; }
            return tslib_1.__awaiter(this, void 0, void 0, function () {
                return tslib_1.__generator(this, function (_a) {
                    return [2 /*return*/, trackDestination.flush(useRetry)];
                });
            });
        }
        var maxSingleEventSize, trackDestination, getMemoryStore, lastKnownDeviceId, usingIdbStore, store, switchToMemoryStore, getIdbStoreOrFallback, _b, beaconBuffer, beaconWindowStart, advanceBeaconWindow, sendEventsList, sendCurrentSequenceEvents, sendStoredEvents, addEvent, getBeaconEvents, dropPendingBeaconEvents;
        var _c;
        return tslib_1.__generator(this, function (_d) {
            switch (_d.label) {
                case 0:
                    maxSingleEventSize = (_c = config.maxSingleEventSizeBytes) !== null && _c !== void 0 ? _c : constants_1.MAX_SINGLE_EVENT_SIZE;
                    trackDestination = new track_destination_1.SessionReplayTrackDestination(tslib_1.__assign(tslib_1.__assign({}, config), { loggerProvider: config.loggerProvider, payloadBatcher: payloadBatcher, workerScript: trackDestinationWorkerScript }));
                    getMemoryStore = function () {
                        return new events_memory_store_1.InMemoryEventsStore({
                            loggerProvider: config.loggerProvider,
                            maxInterval: maxInterval,
                            minInterval: minInterval,
                            maxPersistedEventsSize: maxPersistedEventsSize,
                        });
                    };
                    usingIdbStore = false;
                    switchToMemoryStore = function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
                        var sequences, _a, deviceId_1;
                        return tslib_1.__generator(this, function (_b) {
                            switch (_b.label) {
                                case 0:
                                    if (!usingIdbStore)
                                        return [2 /*return*/];
                                    usingIdbStore = false;
                                    config.loggerProvider.warn('IDB store is experiencing repeated failures; falling back to in-memory event store.');
                                    if (!lastKnownDeviceId) return [3 /*break*/, 2];
                                    return [4 /*yield*/, store.getSequencesToSend()];
                                case 1:
                                    _a = _b.sent();
                                    return [3 /*break*/, 3];
                                case 2:
                                    _a = undefined;
                                    _b.label = 3;
                                case 3:
                                    sequences = _a;
                                    store = getMemoryStore();
                                    if (sequences && lastKnownDeviceId) {
                                        deviceId_1 = lastKnownDeviceId;
                                        sequences.forEach(function (seq) {
                                            sendEventsList({ sequenceId: seq.sequenceId, events: seq.events, sessionId: seq.sessionId, deviceId: deviceId_1 });
                                        });
                                    }
                                    return [2 /*return*/];
                            }
                        });
                    }); };
                    getIdbStoreOrFallback = function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
                        var idb;
                        return tslib_1.__generator(this, function (_a) {
                            switch (_a.label) {
                                case 0: return [4 /*yield*/, events_idb_store_1.SessionReplayEventsIDBStore.new(type, {
                                        loggerProvider: config.loggerProvider,
                                        minInterval: minInterval,
                                        maxInterval: maxInterval,
                                        maxPersistedEventsSize: maxPersistedEventsSize,
                                        apiKey: config.apiKey,
                                        onPersistentFailure: function () {
                                            void switchToMemoryStore();
                                        },
                                    })];
                                case 1:
                                    idb = _a.sent();
                                    if (!idb) {
                                        config.loggerProvider.log('Failed to initialize idb store, falling back to memory store.');
                                        return [2 /*return*/, getMemoryStore()];
                                    }
                                    usingIdbStore = true;
                                    return [2 /*return*/, idb];
                            }
                        });
                    }); };
                    if (!(storeType === 'idb')) return [3 /*break*/, 2];
                    return [4 /*yield*/, getIdbStoreOrFallback()];
                case 1:
                    _b = _d.sent();
                    return [3 /*break*/, 3];
                case 2:
                    _b = getMemoryStore();
                    _d.label = 3;
                case 3:
                    store = _b;
                    beaconBuffer = [];
                    beaconWindowStart = 0;
                    advanceBeaconWindow = function (upToAbsoluteIdx) {
                        if (upToAbsoluteIdx <= beaconWindowStart)
                            return;
                        var trimCount = Math.min(upToAbsoluteIdx - beaconWindowStart, beaconBuffer.length);
                        if (trimCount > 0) {
                            beaconBuffer.splice(0, trimCount);
                            beaconWindowStart = upToAbsoluteIdx;
                        }
                    };
                    sendEventsList = function (_a) {
                        var rawEvents = _a.events, sessionId = _a.sessionId, deviceId = _a.deviceId, sequenceId = _a.sequenceId;
                        // Backstop for events that entered IDB before the per-event size guard in
                        // addCompressedEventToManager (e.g. stored by a previous SDK version or via
                        // storeCurrentSequence/sendStoredEvents which bypass the capture-time check).
                        // Compare UTF-8 byte size, not JS char count, to match the server-side limit.
                        var sizedEvents = rawEvents.map(function (e) { return ({ event: e, bytes: new Blob([e]).size }); });
                        var oversized = sizedEvents.filter(function (s) { return s.bytes > maxSingleEventSize; });
                        if (oversized.length > 0) {
                            config.loggerProvider.warn("Dropping ".concat(oversized.length, " oversized event(s) from session replay sequence before send. Sizes: ").concat(oversized
                                .map(function (s) { return "".concat(Math.round(s.bytes / 1024), " KB"); })
                                .join(', '), ". If this recurs, please open a GitHub issue at https://github.com/amplitude/Amplitude-TypeScript/issues or contact Amplitude support."));
                        }
                        var events = oversized.length > 0 ? sizedEvents.filter(function (s) { return s.bytes <= maxSingleEventSize; }).map(function (s) { return s.event; }) : rawEvents;
                        if (events.length === 0) {
                            store.cleanUpSessionEventsStore(sessionId, sequenceId).catch(function (e) {
                                config.loggerProvider.warn('Failed to clean up session replay events store:', e);
                            });
                            return;
                        }
                        if (config.debugMode) {
                            (0, helpers_1.getStorageSize)()
                                .then(function (_a) {
                                var totalStorageSize = _a.totalStorageSize, percentOfQuota = _a.percentOfQuota, usageDetails = _a.usageDetails;
                                config.loggerProvider.debug("Total storage size: ".concat(totalStorageSize, " KB, percentage of quota: ").concat(percentOfQuota, "%, usage details: ").concat(usageDetails));
                            })
                                .catch(function () {
                                // swallow error
                            });
                        }
                        trackDestination.sendEventsList({
                            events: events,
                            sessionId: sessionId,
                            flushMaxRetries: config.flushMaxRetries,
                            apiKey: config.apiKey,
                            deviceId: deviceId,
                            sampleRate: config.sampleRate,
                            serverZone: config.serverZone,
                            version: config.version,
                            type: type,
                            onComplete: function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
                                return tslib_1.__generator(this, function (_a) {
                                    switch (_a.label) {
                                        case 0: return [4 /*yield*/, store.cleanUpSessionEventsStore(sessionId, sequenceId)];
                                        case 1:
                                            _a.sent();
                                            return [2 /*return*/];
                                    }
                                });
                            }); },
                        });
                    };
                    sendCurrentSequenceEvents = function (_a) {
                        var sessionId = _a.sessionId, deviceId = _a.deviceId;
                        lastKnownDeviceId = deviceId;
                        // Evaluate shouldSend synchronously before the async store read. asyncSetSessionId
                        // updates sessionStartTime immediately after calling sendEvents(), so by the time
                        // storeCurrentSequence resolves the start time would reflect the new session and
                        // the elapsed check would compute ~0ms, silently dropping the previous session's events.
                        if (shouldSend && !shouldSend())
                            return;
                        // Snapshot the absolute end-index before the async store read so that any events
                        // pushed after this point are NOT considered sent and remain in the beacon buffer.
                        var snapshotAbsIdx = beaconWindowStart + beaconBuffer.length;
                        store
                            .storeCurrentSequence(sessionId)
                            .then(function (currentSequence) {
                            if (currentSequence) {
                                advanceBeaconWindow(snapshotAbsIdx);
                                sendEventsList({
                                    sequenceId: currentSequence.sequenceId,
                                    events: currentSequence.events,
                                    sessionId: currentSequence.sessionId,
                                    deviceId: deviceId,
                                });
                            }
                        })
                            .catch(function (e) {
                            config.loggerProvider.warn('Failed to get current sequence of session replay events for session:', e);
                        });
                    };
                    sendStoredEvents = function (_a) {
                        var deviceId = _a.deviceId;
                        return tslib_1.__awaiter(void 0, void 0, void 0, function () {
                            var sequencesToSend;
                            return tslib_1.__generator(this, function (_b) {
                                switch (_b.label) {
                                    case 0:
                                        lastKnownDeviceId = deviceId;
                                        return [4 /*yield*/, store.getSequencesToSend()];
                                    case 1:
                                        sequencesToSend = _b.sent();
                                        if (!(sequencesToSend === null || sequencesToSend === void 0 ? void 0 : sequencesToSend.length)) {
                                            return [2 /*return*/];
                                        }
                                        config.loggerProvider.log("Draining ".concat(sequencesToSend.length, " stored sequence(s) from previous session."));
                                        // These persisted sequences are about to be enqueued back-to-back. Without
                                        // coalescing they flush as N separate POSTs — a request flood on page load. Mark the
                                        // imminent flush to merge same-identity batches (the enqueues below are synchronous, so
                                        // the whole backlog lands in the queue before the deferred flush consumes the flag). Skip
                                        // the single-sequence case where there's nothing to merge.
                                        if (sequencesToSend.length > 1) {
                                            trackDestination.markCoalesceNextFlush();
                                        }
                                        sequencesToSend.forEach(function (sequence) {
                                            sendEventsList({
                                                sequenceId: sequence.sequenceId,
                                                events: sequence.events,
                                                sessionId: sequence.sessionId,
                                                deviceId: deviceId,
                                            });
                                        });
                                        return [2 /*return*/];
                                }
                            });
                        });
                    };
                    addEvent = function (_a) {
                        var event = _a.event, sessionId = _a.sessionId, deviceId = _a.deviceId;
                        lastKnownDeviceId = deviceId;
                        // Capture shouldSend synchronously before the async store write, for the same
                        // reason as sendCurrentSequenceEvents: asyncSetSessionId updates sessionStartTime
                        // synchronously, so evaluating inside the .then() would use the new session's
                        // start time and compute ~0ms elapsed, silently dropping a valid batch.
                        var canSend = !shouldSend || shouldSend();
                        // Record the absolute index of this event in the beacon buffer before the async
                        // store operation. If a batch split occurs, we advance the window up to (but not
                        // including) this event so that it starts the next pending window.
                        var absIdx = beaconWindowStart + beaconBuffer.length;
                        beaconBuffer.push(event.data);
                        store
                            .addEventToCurrentSequence(sessionId, event.data)
                            .then(function (sequenceToSend) {
                            if (sequenceToSend) {
                                if (!canSend) {
                                    // The split atomically moved events to sequencesToSend; without cleanup
                                    // they would be unconditionally replayed on next page load via
                                    // sendStoredEvents, bypassing the min session duration gate. The split
                                    // batch must also be dropped from the beacon buffer so it isn't sent
                                    // via sendBeacon on page unload (potentially attributed to a later session).
                                    store.cleanUpSessionEventsStore(sequenceToSend.sessionId, sequenceToSend.sequenceId).catch(function (e) {
                                        config.loggerProvider.warn('Failed to clean up dropped session replay sequence:', e);
                                    });
                                    advanceBeaconWindow(absIdx);
                                    return;
                                }
                                // Events before absIdx belong to the split batch being sent; advance window.
                                advanceBeaconWindow(absIdx);
                                sendEventsList({
                                    sequenceId: sequenceToSend.sequenceId,
                                    events: sequenceToSend.events,
                                    sessionId: sequenceToSend.sessionId,
                                    deviceId: deviceId,
                                });
                            }
                        })
                            .catch(function (e) {
                            config.loggerProvider.warn('Failed to add event to session replay capture:', e);
                        });
                    };
                    getBeaconEvents = function () { return tslib_1.__spreadArray([], tslib_1.__read(beaconBuffer), false); };
                    dropPendingBeaconEvents = function () {
                        advanceBeaconWindow(beaconWindowStart + beaconBuffer.length);
                    };
                    return [2 /*return*/, {
                            sendCurrentSequenceEvents: sendCurrentSequenceEvents,
                            addEvent: addEvent,
                            sendStoredEvents: sendStoredEvents,
                            flush: flush,
                            getBeaconEvents: getBeaconEvents,
                            dropPendingBeaconEvents: dropPendingBeaconEvents,
                            trackDestination: trackDestination,
                        }];
            }
        });
    });
};
exports.createEventsManager = createEventsManager;
//# sourceMappingURL=events-manager.js.map