"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SyncService = exports.SYNC_TYPES_ORDER = void 0;
const common_1 = require("@nestjs/common");
const luxon_1 = require("luxon");
const decorators_1 = require("../decorators");
const sync_dto_1 = require("../dtos/sync.dto");
const enum_1 = require("../enum");
const base_service_1 = require("./base.service");
const bytes_1 = require("../utils/bytes");
const sync_1 = require("../utils/sync");
const COMPLETE_ID = 'complete';
const MAX_DAYS = 30;
const MAX_DURATION = luxon_1.Duration.fromObject({ days: MAX_DAYS });
const mapSyncAssetV2 = ({ checksum, thumbhash, ...data }) => ({
    ...data,
    checksum: (0, bytes_1.hexOrBufferToBase64)(checksum),
    thumbhash: thumbhash ? (0, bytes_1.hexOrBufferToBase64)(thumbhash) : null,
});
const isEntityBackfillComplete = (createId, checkpoint) => createId === checkpoint?.updateId && checkpoint.extraId === COMPLETE_ID;
const getStartId = (createId, checkpoint) => createId === checkpoint?.updateId ? checkpoint?.extraId : undefined;
const send = (response, item) => {
    response.write((0, sync_1.serialize)(item));
};
const sendEntityBackfillCompleteAck = (response, ackType, id) => {
    send(response, { type: enum_1.SyncEntityType.SyncAckV1, data: {}, ackType, ids: [id, COMPLETE_ID] });
};
exports.SYNC_TYPES_ORDER = [
    enum_1.SyncRequestType.AuthUsersV1,
    enum_1.SyncRequestType.UsersV1,
    enum_1.SyncRequestType.PartnersV1,
    enum_1.SyncRequestType.AssetsV1,
    enum_1.SyncRequestType.AssetsV2,
    enum_1.SyncRequestType.StacksV1,
    enum_1.SyncRequestType.PartnerAssetsV1,
    enum_1.SyncRequestType.PartnerAssetsV2,
    enum_1.SyncRequestType.PartnerStacksV1,
    enum_1.SyncRequestType.AlbumAssetsV1,
    enum_1.SyncRequestType.AlbumAssetsV2,
    enum_1.SyncRequestType.AlbumsV1,
    enum_1.SyncRequestType.AlbumsV2,
    enum_1.SyncRequestType.AlbumUsersV1,
    enum_1.SyncRequestType.AlbumToAssetsV1,
    enum_1.SyncRequestType.AssetExifsV1,
    enum_1.SyncRequestType.AlbumAssetExifsV1,
    enum_1.SyncRequestType.AssetOcrV1,
    enum_1.SyncRequestType.PartnerAssetExifsV1,
    enum_1.SyncRequestType.MemoriesV1,
    enum_1.SyncRequestType.MemoryToAssetsV1,
    enum_1.SyncRequestType.PeopleV1,
    enum_1.SyncRequestType.AssetFacesV1,
    enum_1.SyncRequestType.AssetFacesV2,
    enum_1.SyncRequestType.UserMetadataV1,
    enum_1.SyncRequestType.AssetMetadataV1,
    enum_1.SyncRequestType.AssetEditsV1,
];
const throwSessionRequired = () => {
    throw new common_1.ForbiddenException('Sync endpoints cannot be used with API keys');
};
let SyncService = class SyncService extends base_service_1.BaseService {
    getAcks(auth) {
        const sessionId = auth.session?.id;
        if (!sessionId) {
            return throwSessionRequired();
        }
        return this.syncCheckpointRepository.getAll(sessionId);
    }
    async setAcks(auth, dto) {
        const sessionId = auth.session?.id;
        if (!sessionId) {
            return throwSessionRequired();
        }
        const checkpoints = {};
        for (const ack of dto.acks) {
            const { type } = (0, sync_1.fromAck)(ack);
            if (type === enum_1.SyncEntityType.SyncResetV1) {
                await this.sessionRepository.resetSyncProgress(sessionId);
                return;
            }
            if (!Object.values(enum_1.SyncEntityType).includes(type)) {
                throw new common_1.BadRequestException(`Invalid ack type: ${type}`);
            }
            checkpoints[type] = { sessionId, type, ack };
        }
        await this.syncCheckpointRepository.upsertAll(Object.values(checkpoints));
    }
    async deleteAcks(auth, dto) {
        const sessionId = auth.session?.id;
        if (!sessionId) {
            return throwSessionRequired();
        }
        await this.syncCheckpointRepository.deleteAll(sessionId, dto.types);
    }
    async stream(auth, response, dto) {
        const session = auth.session;
        if (!session) {
            return throwSessionRequired();
        }
        if (dto.reset) {
            await this.sessionRepository.resetSyncProgress(session.id);
        }
        const isPendingSyncReset = await this.sessionRepository.isPendingSyncReset(session.id);
        if (isPendingSyncReset) {
            send(response, { type: enum_1.SyncEntityType.SyncResetV1, ids: ['reset'], data: {} });
            response.end();
            return;
        }
        const checkpoints = await this.syncCheckpointRepository.getAll(session.id);
        const checkpointMap = Object.fromEntries(checkpoints.map(({ type, ack }) => [type, (0, sync_1.fromAck)(ack)]));
        if (this.needsFullSync(checkpointMap)) {
            send(response, { type: enum_1.SyncEntityType.SyncResetV1, ids: ['reset'], data: {} });
            response.end();
            return;
        }
        const { nowId } = await this.syncCheckpointRepository.getNow();
        const options = { nowId, userId: auth.user.id };
        const handlers = {
            [enum_1.SyncRequestType.AssetsV1]: () => this.syncAssetsV1(),
            [enum_1.SyncRequestType.AssetFacesV1]: () => this.syncAssetFacesV1(),
            [enum_1.SyncRequestType.PartnerAssetsV1]: () => this.syncPartnerAssetsV1(),
            [enum_1.SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(),
            [enum_1.SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.UsersV1]: () => this.syncUsersV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.PartnersV1]: () => this.syncPartnersV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.AssetsV2]: () => this.syncAssetsV2(options, response, checkpointMap),
            [enum_1.SyncRequestType.AssetExifsV1]: () => this.syncAssetExifsV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.AssetEditsV1]: () => this.syncAssetEditsV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.PartnerAssetsV2]: () => this.syncPartnerAssetsV2(options, response, checkpointMap, session.id),
            [enum_1.SyncRequestType.AssetMetadataV1]: () => this.syncAssetMetadataV1(options, response, checkpointMap, auth),
            [enum_1.SyncRequestType.PartnerAssetExifsV1]: () => this.syncPartnerAssetExifsV1(options, response, checkpointMap, session.id),
            [enum_1.SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.AlbumsV2]: () => this.syncAlbumsV2(options, response, checkpointMap),
            [enum_1.SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(options, response, checkpointMap, session.id),
            [enum_1.SyncRequestType.AlbumAssetsV2]: () => this.syncAlbumAssetsV2(options, response, checkpointMap, session.id),
            [enum_1.SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id),
            [enum_1.SyncRequestType.AlbumAssetExifsV1]: () => this.syncAlbumAssetExifsV1(options, response, checkpointMap, session.id),
            [enum_1.SyncRequestType.MemoriesV1]: () => this.syncMemoriesV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.MemoryToAssetsV1]: () => this.syncMemoryAssetsV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.StacksV1]: () => this.syncStackV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.PartnerStacksV1]: () => this.syncPartnerStackV1(options, response, checkpointMap, session.id),
            [enum_1.SyncRequestType.PeopleV1]: () => this.syncPeopleV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.AssetFacesV2]: () => this.syncAssetFacesV2(options, response, checkpointMap),
            [enum_1.SyncRequestType.UserMetadataV1]: () => this.syncUserMetadataV1(options, response, checkpointMap),
            [enum_1.SyncRequestType.AssetOcrV1]: () => this.syncAssetOcrV1(options, response, checkpointMap, auth),
        };
        for (const type of exports.SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
            const handler = handlers[type];
            await handler();
        }
        send(response, { type: enum_1.SyncEntityType.SyncCompleteV1, ids: [nowId], data: {} });
        response.end();
    }
    async onAuditTableCleanup() {
        const pruneThreshold = MAX_DAYS + 1;
        await this.syncRepository.album.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.albumUser.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.albumToAsset.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.asset.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.assetFace.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.assetMetadata.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.assetEdit.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.memory.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.memoryToAsset.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.partner.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.person.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.stack.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.user.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.userMetadata.cleanupAuditTable(pruneThreshold);
        await this.syncRepository.assetOcr.cleanupAuditTable(pruneThreshold);
    }
    needsFullSync(checkpointMap) {
        const completeAck = checkpointMap[enum_1.SyncEntityType.SyncCompleteV1];
        if (!completeAck) {
            return false;
        }
        const milliseconds = Number.parseInt(completeAck.updateId.replaceAll('-', '').slice(0, 12), 16);
        return luxon_1.DateTime.fromMillis(milliseconds) < luxon_1.DateTime.now().minus(MAX_DURATION);
    }
    async syncAuthUsersV1(options, response, checkpointMap) {
        const upsertType = enum_1.SyncEntityType.AuthUserV1;
        const upserts = this.syncRepository.authUser.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, profileImagePath, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data: { ...data, hasProfileImage: !!profileImagePath } });
        }
    }
    async syncUsersV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.UserDeleteV1;
        const deletes = this.syncRepository.user.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.UserV1;
        const upserts = this.syncRepository.user.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, profileImagePath, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data: { ...data, hasProfileImage: !!profileImagePath } });
        }
    }
    async syncPartnersV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.PartnerDeleteV1;
        const deletes = this.syncRepository.partner.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.PartnerV1;
        const upserts = this.syncRepository.partner.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    syncAssetsV1() {
        throw new common_1.BadRequestException('SyncRequestType.AssetsV1 is deprecated, use SyncRequestType.AssetsV2 instead');
    }
    async syncAssetsV2(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.AssetDeleteV1;
        const deletes = this.syncRepository.asset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.AssetV2;
        const upserts = this.syncRepository.asset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV2(data) });
        }
    }
    syncPartnerAssetsV1() {
        throw new common_1.BadRequestException('SyncRequestType.PartnerAssetsV1 is deprecated, use SyncRequestType.PartnerAssetsV2 instead');
    }
    async syncPartnerAssetsV2(options, response, checkpointMap, sessionId) {
        const deleteType = enum_1.SyncEntityType.PartnerAssetDeleteV1;
        const deletes = this.syncRepository.partnerAsset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const backfillType = enum_1.SyncEntityType.PartnerAssetBackfillV2;
        const backfillCheckpoint = checkpointMap[backfillType];
        const partners = await this.syncRepository.partner.getCreatedAfter({
            ...options,
            afterCreateId: backfillCheckpoint?.updateId,
        });
        const upsertType = enum_1.SyncEntityType.PartnerAssetV2;
        const upsertCheckpoint = checkpointMap[upsertType];
        if (upsertCheckpoint) {
            const endId = upsertCheckpoint.updateId;
            for (const partner of partners) {
                const createId = partner.createId;
                if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
                    continue;
                }
                const startId = getStartId(createId, backfillCheckpoint);
                const backfill = this.syncRepository.partnerAsset.getBackfill({ ...options, afterUpdateId: startId, beforeUpdateId: endId }, partner.sharedById);
                for await (const { updateId, ...data } of backfill) {
                    send(response, {
                        type: backfillType,
                        ids: [createId, updateId],
                        data: mapSyncAssetV2(data),
                    });
                }
                sendEntityBackfillCompleteAck(response, backfillType, createId);
            }
        }
        else if (partners.length > 0) {
            await this.upsertBackfillCheckpoint({
                type: backfillType,
                sessionId,
                createId: partners.at(-1).createId,
            });
        }
        const upserts = this.syncRepository.partnerAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV2(data) });
        }
    }
    async syncAssetExifsV1(options, response, checkpointMap) {
        const upsertType = enum_1.SyncEntityType.AssetExifV1;
        const upserts = this.syncRepository.assetExif.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncAssetEditsV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.AssetEditDeleteV1;
        const deletes = this.syncRepository.assetEdit.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.AssetEditV1;
        const upserts = this.syncRepository.assetEdit.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncPartnerAssetExifsV1(options, response, checkpointMap, sessionId) {
        const backfillType = enum_1.SyncEntityType.PartnerAssetExifBackfillV1;
        const backfillCheckpoint = checkpointMap[backfillType];
        const partners = await this.syncRepository.partner.getCreatedAfter({
            ...options,
            afterCreateId: backfillCheckpoint?.updateId,
        });
        const upsertType = enum_1.SyncEntityType.PartnerAssetExifV1;
        const upsertCheckpoint = checkpointMap[upsertType];
        if (upsertCheckpoint) {
            const endId = upsertCheckpoint.updateId;
            for (const partner of partners) {
                const createId = partner.createId;
                if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
                    continue;
                }
                const startId = getStartId(createId, backfillCheckpoint);
                const backfill = this.syncRepository.partnerAssetExif.getBackfill({ ...options, afterUpdateId: startId, beforeUpdateId: endId }, partner.sharedById);
                for await (const { updateId, ...data } of backfill) {
                    send(response, { type: backfillType, ids: [partner.createId, updateId], data });
                }
                sendEntityBackfillCompleteAck(response, backfillType, partner.createId);
            }
        }
        else if (partners.length > 0) {
            await this.upsertBackfillCheckpoint({
                type: backfillType,
                sessionId,
                createId: partners.at(-1).createId,
            });
        }
        const upserts = this.syncRepository.partnerAssetExif.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncAlbumsV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.AlbumDeleteV1;
        const deletes = this.syncRepository.album.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.AlbumV1;
        const upserts = this.syncRepository.album.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            const albumUsers = await this.syncRepository.album.getAlbumUsers(data.id);
            send(response, { type: upsertType, ids: [updateId], data: (0, sync_dto_1.syncAlbumV2ToV1)(data, albumUsers) });
        }
    }
    async syncAlbumsV2(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.AlbumDeleteV1;
        const deletes = this.syncRepository.album.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.AlbumV2;
        const upserts = this.syncRepository.album.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncAlbumUsersV1(options, response, checkpointMap, sessionId) {
        const deleteType = enum_1.SyncEntityType.AlbumUserDeleteV1;
        const deletes = this.syncRepository.albumUser.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const backfillType = enum_1.SyncEntityType.AlbumUserBackfillV1;
        const backfillCheckpoint = checkpointMap[backfillType];
        const albums = await this.syncRepository.album.getCreatedAfter({
            ...options,
            afterCreateId: backfillCheckpoint?.updateId,
        });
        const upsertType = enum_1.SyncEntityType.AlbumUserV1;
        const upsertCheckpoint = checkpointMap[upsertType];
        if (upsertCheckpoint) {
            const endId = upsertCheckpoint.updateId;
            for (const album of albums) {
                const createId = album.createId;
                if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
                    continue;
                }
                const startId = getStartId(createId, backfillCheckpoint);
                const backfill = this.syncRepository.albumUser.getBackfill({ ...options, afterUpdateId: startId, beforeUpdateId: endId }, album.id);
                for await (const { updateId, ...data } of backfill) {
                    send(response, { type: backfillType, ids: [createId, updateId], data });
                }
                sendEntityBackfillCompleteAck(response, backfillType, createId);
            }
        }
        else if (albums.length > 0) {
            await this.upsertBackfillCheckpoint({
                type: backfillType,
                sessionId,
                createId: albums.at(-1).createId,
            });
        }
        const upserts = this.syncRepository.albumUser.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    syncAlbumAssetsV1() {
        throw new common_1.BadRequestException('SyncRequestType.AlbumAssetsV1 is deprecated, use SyncRequestType.AlbumAssetsV2 instead');
    }
    async syncAlbumAssetsV2(options, response, checkpointMap, sessionId) {
        const backfillType = enum_1.SyncEntityType.AlbumAssetBackfillV2;
        const backfillCheckpoint = checkpointMap[backfillType];
        const albums = await this.syncRepository.album.getCreatedAfter({
            ...options,
            afterCreateId: backfillCheckpoint?.updateId,
        });
        const updateType = enum_1.SyncEntityType.AlbumAssetUpdateV2;
        const createType = enum_1.SyncEntityType.AlbumAssetCreateV2;
        const updateCheckpoint = checkpointMap[updateType];
        const createCheckpoint = checkpointMap[createType];
        if (createCheckpoint) {
            const endId = createCheckpoint.updateId;
            for (const album of albums) {
                const createId = album.createId;
                if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
                    continue;
                }
                const startId = getStartId(createId, backfillCheckpoint);
                const backfill = this.syncRepository.albumAsset.getBackfill({ ...options, afterUpdateId: startId, beforeUpdateId: endId }, album.id, options.userId);
                for await (const { updateId, ...data } of backfill) {
                    send(response, { type: backfillType, ids: [createId, updateId], data: mapSyncAssetV2(data) });
                }
                sendEntityBackfillCompleteAck(response, backfillType, createId);
            }
        }
        else if (albums.length > 0) {
            await this.upsertBackfillCheckpoint({
                type: backfillType,
                sessionId,
                createId: albums.at(-1).createId,
            });
        }
        if (createCheckpoint) {
            const updates = this.syncRepository.albumAsset.getUpdates({ ...options, ack: updateCheckpoint }, createCheckpoint);
            for await (const { updateId, ...data } of updates) {
                send(response, { type: updateType, ids: [updateId], data: mapSyncAssetV2(data) });
            }
        }
        const creates = this.syncRepository.albumAsset.getCreates({ ...options, ack: createCheckpoint });
        let first = true;
        for await (const { updateId, ...data } of creates) {
            if (first) {
                send(response, {
                    type: enum_1.SyncEntityType.SyncAckV1,
                    data: {},
                    ackType: enum_1.SyncEntityType.AlbumAssetUpdateV2,
                    ids: [options.nowId],
                });
                first = false;
            }
            send(response, { type: createType, ids: [updateId], data: mapSyncAssetV2(data) });
        }
    }
    async syncAlbumAssetExifsV1(options, response, checkpointMap, sessionId) {
        const backfillType = enum_1.SyncEntityType.AlbumAssetExifBackfillV1;
        const backfillCheckpoint = checkpointMap[backfillType];
        const albums = await this.syncRepository.album.getCreatedAfter({
            ...options,
            afterCreateId: backfillCheckpoint?.updateId,
        });
        const updateType = enum_1.SyncEntityType.AlbumAssetExifUpdateV1;
        const createType = enum_1.SyncEntityType.AlbumAssetExifCreateV1;
        const upsertCheckpoint = checkpointMap[updateType];
        const createCheckpoint = checkpointMap[createType];
        if (createCheckpoint) {
            const endId = createCheckpoint.updateId;
            for (const album of albums) {
                const createId = album.createId;
                if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
                    continue;
                }
                const startId = getStartId(createId, backfillCheckpoint);
                const backfill = this.syncRepository.albumAssetExif.getBackfill({ ...options, afterUpdateId: startId, beforeUpdateId: endId }, album.id);
                for await (const { updateId, ...data } of backfill) {
                    send(response, { type: backfillType, ids: [createId, updateId], data });
                }
                sendEntityBackfillCompleteAck(response, backfillType, createId);
            }
        }
        else if (albums.length > 0) {
            await this.upsertBackfillCheckpoint({
                type: backfillType,
                sessionId,
                createId: albums.at(-1).createId,
            });
        }
        if (createCheckpoint) {
            const updates = this.syncRepository.albumAssetExif.getUpdates({ ...options, ack: upsertCheckpoint }, createCheckpoint);
            for await (const { updateId, ...data } of updates) {
                send(response, { type: updateType, ids: [updateId], data });
            }
        }
        const creates = this.syncRepository.albumAssetExif.getCreates({ ...options, ack: createCheckpoint });
        let first = true;
        for await (const { updateId, ...data } of creates) {
            if (first) {
                send(response, {
                    type: enum_1.SyncEntityType.SyncAckV1,
                    data: {},
                    ackType: enum_1.SyncEntityType.AlbumAssetExifUpdateV1,
                    ids: [options.nowId],
                });
                first = false;
            }
            send(response, { type: createType, ids: [updateId], data });
        }
    }
    async syncAlbumToAssetsV1(options, response, checkpointMap, sessionId) {
        const deleteType = enum_1.SyncEntityType.AlbumToAssetDeleteV1;
        const deletes = this.syncRepository.albumToAsset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const backfillType = enum_1.SyncEntityType.AlbumToAssetBackfillV1;
        const backfillCheckpoint = checkpointMap[backfillType];
        const albums = await this.syncRepository.album.getCreatedAfter({
            ...options,
            afterCreateId: backfillCheckpoint?.updateId,
        });
        const upsertType = enum_1.SyncEntityType.AlbumToAssetV1;
        const upsertCheckpoint = checkpointMap[upsertType];
        if (upsertCheckpoint) {
            const endId = upsertCheckpoint.updateId;
            for (const album of albums) {
                const createId = album.createId;
                if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
                    continue;
                }
                const startId = getStartId(createId, backfillCheckpoint);
                const backfill = this.syncRepository.albumToAsset.getBackfill({ ...options, afterUpdateId: startId, beforeUpdateId: endId }, album.id);
                for await (const { updateId, ...data } of backfill) {
                    send(response, { type: backfillType, ids: [createId, updateId], data });
                }
                sendEntityBackfillCompleteAck(response, backfillType, createId);
            }
        }
        else if (albums.length > 0) {
            await this.upsertBackfillCheckpoint({
                type: backfillType,
                sessionId,
                createId: albums.at(-1).createId,
            });
        }
        const upserts = this.syncRepository.albumToAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncMemoriesV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.MemoryDeleteV1;
        const deletes = this.syncRepository.memory.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.MemoryV1;
        const upserts = this.syncRepository.memory.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncMemoryAssetsV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.MemoryToAssetDeleteV1;
        const deletes = this.syncRepository.memoryToAsset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.MemoryToAssetV1;
        const upserts = this.syncRepository.memoryToAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncStackV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.StackDeleteV1;
        const deletes = this.syncRepository.stack.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.StackV1;
        const upserts = this.syncRepository.stack.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncPartnerStackV1(options, response, checkpointMap, sessionId) {
        const deleteType = enum_1.SyncEntityType.PartnerStackDeleteV1;
        const deletes = this.syncRepository.partnerStack.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const backfillType = enum_1.SyncEntityType.PartnerStackBackfillV1;
        const backfillCheckpoint = checkpointMap[backfillType];
        const partners = await this.syncRepository.partner.getCreatedAfter({
            ...options,
            afterCreateId: backfillCheckpoint?.updateId,
        });
        const upsertType = enum_1.SyncEntityType.PartnerStackV1;
        const upsertCheckpoint = checkpointMap[upsertType];
        if (upsertCheckpoint) {
            const endId = upsertCheckpoint.updateId;
            for (const partner of partners) {
                const createId = partner.createId;
                if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
                    continue;
                }
                const startId = getStartId(createId, backfillCheckpoint);
                const backfill = this.syncRepository.partnerStack.getBackfill({ ...options, afterUpdateId: startId, beforeUpdateId: endId }, partner.sharedById);
                for await (const { updateId, ...data } of backfill) {
                    send(response, {
                        type: backfillType,
                        ids: [createId, updateId],
                        data,
                    });
                }
                sendEntityBackfillCompleteAck(response, backfillType, createId);
            }
        }
        else if (partners.length > 0) {
            await this.upsertBackfillCheckpoint({
                type: backfillType,
                sessionId,
                createId: partners.at(-1).createId,
            });
        }
        const upserts = this.syncRepository.partnerStack.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncPeopleV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.PersonDeleteV1;
        const deletes = this.syncRepository.person.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.PersonV1;
        const upserts = this.syncRepository.person.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    syncAssetFacesV1() {
        throw new common_1.BadRequestException('SyncRequestType.AssetFacesV1 is deprecated, use SyncRequestType.AssetFacesV2 instead');
    }
    async syncAssetFacesV2(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.AssetFaceDeleteV1;
        const deletes = this.syncRepository.assetFace.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.AssetFaceV2;
        const upserts = this.syncRepository.assetFace.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncUserMetadataV1(options, response, checkpointMap) {
        const deleteType = enum_1.SyncEntityType.UserMetadataDeleteV1;
        const deletes = this.syncRepository.userMetadata.getDeletes({ ...options, ack: checkpointMap[deleteType] });
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.UserMetadataV1;
        const upserts = this.syncRepository.userMetadata.getUpserts({ ...options, ack: checkpointMap[upsertType] });
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncAssetMetadataV1(options, response, checkpointMap, auth) {
        const deleteType = enum_1.SyncEntityType.AssetMetadataDeleteV1;
        const deletes = this.syncRepository.assetMetadata.getDeletes({ ...options, ack: checkpointMap[deleteType] }, auth.user.id);
        for await (const { id, ...data } of deletes) {
            send(response, { type: deleteType, ids: [id], data });
        }
        const upsertType = enum_1.SyncEntityType.AssetMetadataV1;
        const upserts = this.syncRepository.assetMetadata.getUpserts({ ...options, ack: checkpointMap[upsertType] }, auth.user.id);
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async syncAssetOcrV1(options, response, checkpointMap, auth) {
        const deleteType = enum_1.SyncEntityType.AssetOcrDeleteV1;
        const deletes = this.syncRepository.assetOcr.getDeletes({ ...options, ack: checkpointMap[deleteType] }, auth.user.id);
        for await (const row of deletes) {
            send(response, { type: deleteType, ids: [row.id], data: row });
        }
        const upsertType = enum_1.SyncEntityType.AssetOcrV1;
        const upserts = this.syncRepository.assetOcr.getUpserts({ ...options, ack: checkpointMap[upsertType] }, auth.user.id);
        for await (const { updateId, ...data } of upserts) {
            send(response, { type: upsertType, ids: [updateId], data });
        }
    }
    async upsertBackfillCheckpoint(item) {
        const { type, sessionId, createId } = item;
        await this.syncCheckpointRepository.upsertAll([
            {
                type,
                sessionId,
                ack: (0, sync_1.toAck)({
                    type,
                    updateId: createId,
                    extraId: COMPLETE_ID,
                }),
            },
        ]);
    }
};
exports.SyncService = SyncService;
__decorate([
    (0, decorators_1.OnJob)({ name: enum_1.JobName.AuditTableCleanup, queue: enum_1.QueueName.BackgroundTask }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], SyncService.prototype, "onAuditTableCleanup", null);
exports.SyncService = SyncService = __decorate([
    (0, common_1.Injectable)()
], SyncService);
//# sourceMappingURL=sync.service.js.map