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());
    });
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var m = o[Symbol.asyncIterator], i;
    return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
import { defer, from, map, combineLatest } from 'rxjs';
import BroadcastChannelSender from '../../../../../backend/channels/BroadcastChannelSender';
import { broadcastStatus } from '../../../../../backend/channels/broadcastStatus';
import { EntryType } from '../../../../../CozoDb/types/entities';
import { mapTransactionToEntity } from '../../../../../CozoDb/mapping';
import { dateToNumber } from '../../../../../utils/date';
import { QueuePriority } from '../../../../../QueueManager/types';
import { serializeError } from 'serialize-error';
import { fetchCyberlinksAndResolveParticles, extractCybelinksFromTransaction, } from '../utils/links';
import { createLoopObservable } from '../utils/rxjs';
import { BLOCKCHAIN_SYNC_INTERVAL, MAX_PARRALEL_TRANSACTIONS, SENSE_TRANSACTIONS, } from '../consts';
import { changeSyncStatus } from '../../utils';
import { fetchTransactionsIterable } from '../../../dataSource/blockchain/requests';
import { syncMyChats } from './services/chat';
class SyncTransactionsLoop {
    get loop$() {
        return this._loop$;
    }
    constructor(deps, particlesResolver) {
        this.statusApi = broadcastStatus('transaction', new BroadcastChannelSender());
        this.params = {
            myAddress: null,
            followings: [],
        };
        if (!deps.params$) {
            throw new Error('params$ is not defined');
        }
        this.particlesResolver = particlesResolver;
        deps.dbInstance$.subscribe((db) => {
            this.db = db;
        });
        deps.params$.subscribe((params) => {
            this.params = params;
        });
        this.isInitialized$ = combineLatest([
            deps.dbInstance$,
            deps.params$,
            particlesResolver.isInitialized$,
        ]).pipe(map(([dbInstance, params, syncQueueInitialized]) => !!dbInstance &&
            !!syncQueueInitialized &&
            !!params.cyberIndexUrl &&
            !!params.myAddress));
    }
    start() {
        this._loop$ = createLoopObservable(BLOCKCHAIN_SYNC_INTERVAL, this.isInitialized$, defer(() => from(this.syncAllTransactions())), () => this.statusApi.sendStatus('in-progress'));
        this._loop$.subscribe({
            next: (result) => this.statusApi.sendStatus('idle'),
            error: (err) => this.statusApi.sendStatus('error', err.toString()),
        });
        return this;
    }
    syncAllTransactions() {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (this.params.myAddress) {
                    yield this.syncTransactions(this.params.myAddress, this.params.myAddress, true);
                    this.statusApi.sendStatus('in-progress', `sync my chats`);
                    yield syncMyChats(this.db, this.params.myAddress);
                    this.statusApi.sendStatus('idle');
                }
            }
            catch (err) {
                const serializedError = serializeError(err);
                console.error('>>> syncAllTransactions', JSON.stringify(serializedError, null, 2));
                throw err;
            }
        });
    }
    syncTransactions(myAddress, address, addCyberlinksToSync = false) {
        var _a, e_1, _b, _c;
        var _d, _e;
        return __awaiter(this, void 0, void 0, function* () {
            this.statusApi.sendStatus('in-progress', `sync ${address}...`);
            const { timestampRead, unreadCount, timestampUpdate } = yield this.db.getSyncStatus(myAddress, address);
            const transactionsAsyncIterable = fetchTransactionsIterable(this.params.cyberIndexUrl, address, timestampUpdate + 1);
            let count = 0;
            let lastTransaction;
            try {
                for (var _f = true, transactionsAsyncIterable_1 = __asyncValues(transactionsAsyncIterable), transactionsAsyncIterable_1_1; transactionsAsyncIterable_1_1 = yield transactionsAsyncIterable_1.next(), _a = transactionsAsyncIterable_1_1.done, !_a;) {
                    _c = transactionsAsyncIterable_1_1.value;
                    _f = false;
                    try {
                        const batch = _c;
                        const items = batch.filter((t) => SENSE_TRANSACTIONS.includes(t.type));
                        console.log('--------syncTransactions start', address, items.length, (_d = items.at(0)) === null || _d === void 0 ? void 0 : _d.transaction.block.timestamp);
                        if (items.length > 0) {
                            if (!lastTransaction) {
                                lastTransaction = items.at(0);
                            }
                            const transactions = items.map((i) => mapTransactionToEntity(address, i));
                            count += items.length;
                            yield this.db.putTransactions(transactions);
                            const { tweets, particlesFound, links } = extractCybelinksFromTransaction(items);
                            const tweetCids = Object.keys(tweets);
                            this.statusApi.sendStatus('in-progress', `sync ${address} batch processing - links: ${links.length}, tweets: ${tweetCids.length}, particles: ${particlesFound.length}...`);
                            yield this.particlesResolver.enqueue(tweetCids.map((cid) => ({
                                id: cid,
                                priority: QueuePriority.HIGH,
                            })));
                            if (addCyberlinksToSync && links.length > 0) {
                                yield this.db.putCyberlinks(links.map((link) => (Object.assign(Object.assign({}, link), { neuron: '' }))));
                                if (particlesFound.length > 0) {
                                    yield this.particlesResolver.enqueue(particlesFound.map((cid) => ({
                                        id: cid,
                                        priority: QueuePriority.LOW,
                                    })));
                                }
                                const batchSize = MAX_PARRALEL_TRANSACTIONS;
                                for (let i = 0; i < tweetCids.length; i += batchSize) {
                                    const batch = tweetCids.slice(i, i + batchSize);
                                    const syncStatusEntities = yield Promise.all(batch.map((cid) => __awaiter(this, void 0, void 0, function* () {
                                        const { timestamp, direction, from, to } = tweets[cid];
                                        const syncStatus = {
                                            ownerId: myAddress,
                                            id: cid,
                                            entryType: EntryType.particle,
                                            timestampUpdate: timestamp,
                                            timestampRead: timestamp,
                                            unreadCount: 0,
                                            lastId: direction === 'from' ? from : to,
                                            disabled: false,
                                            meta: { direction },
                                        };
                                        const links = yield fetchCyberlinksAndResolveParticles(this.params.cyberIndexUrl, cid, timestamp, this.particlesResolver, QueuePriority.HIGH);
                                        return links.length > 0
                                            ? changeSyncStatus(syncStatus, links)
                                            : syncStatus;
                                    })));
                                    if (syncStatusEntities.length > 0) {
                                        this.db.putSyncStatus(syncStatusEntities);
                                    }
                                }
                            }
                        }
                    }
                    finally {
                        _f = true;
                    }
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (!_f && !_a && (_b = transactionsAsyncIterable_1.return)) yield _b.call(transactionsAsyncIterable_1);
                }
                finally { if (e_1) throw e_1.error; }
            }
            const unreadTransactionsCount = unreadCount + count;
            if (lastTransaction) {
                this.db.putSyncStatus({
                    ownerId: myAddress,
                    entryType: EntryType.transactions,
                    id: address,
                    timestampUpdate: dateToNumber(lastTransaction.transaction.block.timestamp),
                    unreadCount: unreadTransactionsCount,
                    timestampRead,
                    disabled: false,
                    lastId: lastTransaction.transaction_hash,
                    meta: { memo: ((_e = lastTransaction === null || lastTransaction === void 0 ? void 0 : lastTransaction.transaction) === null || _e === void 0 ? void 0 : _e.memo) || '' },
                });
            }
        });
    }
}
export default SyncTransactionsLoop;
//# sourceMappingURL=SyncTransactionsLoop.js.map