var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { BehaviorSubject, filter, mergeMap, tap, map, combineLatest, share, EMPTY, } from 'rxjs';
import BroadcastChannelSender from '../../../../../backend/channels/BroadcastChannelSender';
import { broadcastStatus } from '../../../../../backend/channels/broadcastStatus';
import { SyncQueueStatus } from '../../../../../CozoDb/types/entities';
import { QueuePriority } from '../../../../../QueueManager/types';
const QUEUE_BATCH_SIZE = 100;
class ParticlesResolverQueue {
    get queue() {
        return this._syncQueue$.getValue();
    }
    get loop$() {
        return this._loop$;
    }
    constructor(deps) {
        this.statusApi = broadcastStatus('resolver', new BroadcastChannelSender());
        this._syncQueue$ = new BehaviorSubject(new Map());
        if (!deps.waitForParticleResolve) {
            throw new Error('waitForParticleResolve is not defined');
        }
        this.waitForParticleResolve = deps.waitForParticleResolve;
        deps.dbInstance$.subscribe((db) => __awaiter(this, void 0, void 0, function* () {
            this.db = db;
            yield this.loadSyncQueue();
        }));
        this.isInitialized$ = combineLatest([
            deps.dbInstance$,
            deps.ipfsInstance$,
        ]).pipe(map(([dbInstance, ipfsInstance]) => !!ipfsInstance && !!dbInstance));
    }
    processSyncQueue(pendingItems) {
        return __awaiter(this, void 0, void 0, function* () {
            const batchSize = pendingItems.length;
            this.statusApi.sendStatus('in-progress', `processing batch ${batchSize}/${batchSize} batch. ${this.queue.size} pending...`);
            let i = batchSize;
            yield Promise.all(pendingItems.map((item) => __awaiter(this, void 0, void 0, function* () {
                const { id } = item;
                return this.waitForParticleResolve(id, QueuePriority.MEDIUM).then((result) => __awaiter(this, void 0, void 0, function* () {
                    if (result.status === 'not_found') {
                        yield this.db.updateSyncQueue({
                            id,
                            status: SyncQueueStatus.error,
                        });
                    }
                    else {
                        yield this.db.removeSyncQueue(id);
                    }
                    const queue = this._syncQueue$.value;
                    queue.delete(id);
                    i--;
                    this._syncQueue$.next(queue);
                    this.statusApi.sendStatus('in-progress', `processing batch ${batchSize - i}/${batchSize} batch. ${this.queue.size} pending...`);
                }));
            })));
        });
    }
    start() {
        const source$ = this.isInitialized$.pipe(tap((q) => console.log(`sync queue isInitialized - ${q}`)), filter((isInitialized) => isInitialized === true), mergeMap(() => this._syncQueue$), tap((q) => console.log(`sync queue - ${q.size}`)), filter((q) => q.size > 0), mergeMap((queue) => {
            const list = [...queue.values()];
            const executingCount = list.filter((i) => i.status === SyncQueueStatus.executing).length;
            const batchSize = QUEUE_BATCH_SIZE - executingCount;
            if (batchSize > 0) {
                const pendingItems = list
                    .filter((i) => i.status === SyncQueueStatus.pending)
                    .sort((a, b) => {
                    return a.priority - b.priority;
                })
                    .slice(0, batchSize);
                if (pendingItems.length > 0) {
                    pendingItems.forEach((i) => {
                        queue.set(i.id, Object.assign(Object.assign({}, i), { status: SyncQueueStatus.executing }));
                    });
                    this._syncQueue$.next(queue);
                    this.statusApi.sendStatus('in-progress', `starting...`);
                    return this.processSyncQueue(pendingItems);
                }
            }
            return EMPTY;
        }));
        this._loop$ = source$.pipe(share());
        this._loop$.subscribe({
            next: (result) => {
                this.statusApi.sendStatus('idle');
            },
            error: (err) => this.statusApi.sendStatus('error', err.toString()),
        });
        return this;
    }
    enqueue(items) {
        return __awaiter(this, void 0, void 0, function* () {
            if (items.length === 0) {
                return;
            }
            yield this.db.putSyncQueue(items);
            const queue = this._syncQueue$.value;
            items.forEach((item) => queue.set(item.id, Object.assign(Object.assign({}, item), { status: SyncQueueStatus.pending })));
            this._syncQueue$.next(queue);
        });
    }
    loadSyncQueue() {
        return __awaiter(this, void 0, void 0, function* () {
            const queue = yield this.db.getSyncQueue({
                statuses: [SyncQueueStatus.pending],
            }).then((items) => new Map(items.map((item) => [item.id, item])));
            this._syncQueue$.next(new Map([...queue, ...this.queue]));
        });
    }
}
export default ParticlesResolverQueue;
//# sourceMappingURL=ParticlesResolverQueue.js.map