"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);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlbumRepository = void 0;
const common_1 = require("@nestjs/common");
const kysely_1 = require("kysely");
const postgres_1 = require("kysely/helpers/postgres");
const nestjs_kysely_1 = require("nestjs-kysely");
const database_1 = require("../database");
const decorators_1 = require("../decorators");
const enum_1 = require("../enum");
const database_2 = require("../utils/database");
const withAlbumUsers = (authUserId) => (eb) => (0, postgres_1.jsonArrayFrom)(eb
    .selectFrom('album_user')
    .innerJoin('user', 'user.id', 'album_user.userId')
    .whereRef('album_user.albumId', '=', 'album.id')
    .select('album_user.role')
    .select((eb) => (0, postgres_1.jsonObjectFrom)(eb.selectFrom(database_2.dummy).select(database_1.columns.user)).$notNull().as('user'))
    .orderBy('album_user.role')
    .$if(!!authUserId, (qb) => qb.orderBy((eb) => eb('album_user.userId', '=', authUserId), 'desc'))
    .orderBy('user.name', 'asc'))
    .$notNull()
    .as('albumUsers');
const withSharedLink = (eb) => (0, postgres_1.jsonArrayFrom)(eb.selectFrom('shared_link').selectAll('shared_link').whereRef('shared_link.albumId', '=', 'album.id')).as('sharedLinks');
const withAssets = (eb) => {
    return eb
        .selectFrom((eb) => eb
        .selectFrom('asset')
        .selectAll('asset')
        .leftJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
        .select((eb) => eb.table('asset_exif').$castTo().as('exifInfo'))
        .innerJoin('album_asset', 'album_asset.assetId', 'asset.id')
        .whereRef('album_asset.albumId', '=', 'album.id')
        .where('asset.deletedAt', 'is', null)
        .$call(database_2.withDefaultVisibility)
        .orderBy('asset.fileCreatedAt', 'desc')
        .as('asset'))
        .select((eb) => eb.fn.jsonAgg('asset').as('assets'))
        .as('assets');
};
const isAlbumOwned = (ownerId) => (eb) => eb.exists(eb
    .selectFrom('album_user')
    .whereRef('album_user.albumId', '=', 'album.id')
    .where('album_user.role', '=', enum_1.AlbumUserRole.Owner)
    .where('album_user.userId', '=', ownerId));
let AlbumRepository = class AlbumRepository {
    db;
    constructor(db) {
        this.db = db;
    }
    getById(id, options, authUserId) {
        return this.db
            .with('album_user', (qb) => qb.selectFrom('album_user').selectAll().where('album_user.albumId', '=', id))
            .selectFrom('album')
            .selectAll('album')
            .where('album.id', '=', id)
            .where('album.deletedAt', 'is', null)
            .select(withAlbumUsers(authUserId))
            .select(withSharedLink)
            .$if(options.withAssets, (eb) => eb.select(withAssets))
            .$narrowType()
            .executeTakeFirst();
    }
    getByAssetId(ownerId, assetId) {
        return this.db
            .selectFrom('album')
            .selectAll('album')
            .innerJoin('album_asset', 'album_asset.albumId', 'album.id')
            .where((eb) => eb.exists(eb
            .selectFrom('album_user')
            .whereRef('album_user.albumId', '=', 'album.id')
            .where('album_user.userId', '=', ownerId)))
            .where('album_asset.assetId', '=', assetId)
            .where('album.deletedAt', 'is', null)
            .select(withAlbumUsers(ownerId))
            .orderBy('album.createdAt', 'desc')
            .execute();
    }
    async getByAssetIds(ownerId, assetIds) {
        if (assetIds.length === 0) {
            return new Map();
        }
        const results = await this.db
            .selectFrom('album')
            .select('album.id')
            .innerJoin('album_asset', 'album_asset.albumId', 'album.id')
            .where((eb) => eb.exists(eb
            .selectFrom('album_user')
            .whereRef('album_user.albumId', '=', 'album.id')
            .where('album_user.userId', '=', ownerId)))
            .where('album_asset.assetId', 'in', assetIds)
            .where('album.deletedAt', 'is', null)
            .select('album_asset.assetId')
            .execute();
        const map = new Map();
        for (const row of results) {
            const existing = map.get(row.assetId) ?? [];
            existing.push(row.id);
            map.set(row.assetId, existing);
        }
        return map;
    }
    async getMetadataForIds(ids) {
        if (ids.length === 0) {
            return [];
        }
        return (this.db
            .selectFrom('asset')
            .$call(database_2.withDefaultVisibility)
            .innerJoin('album_asset', 'album_asset.assetId', 'asset.id')
            .select('album_asset.albumId as albumId')
            .select((eb) => eb.fn.min((0, kysely_1.sql) `("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('startDate'))
            .select((eb) => eb.fn.max((0, kysely_1.sql) `("asset"."localDateTime" AT TIME ZONE 'UTC'::text)::date`).as('endDate'))
            .select((eb) => eb.fn.max('asset.updatedAt').as('lastModifiedAssetTimestamp'))
            .select((eb) => (0, kysely_1.sql) `${eb.fn.count('asset.id')}::int`.as('assetCount'))
            .where('album_asset.albumId', 'in', ids)
            .where('asset.deletedAt', 'is', null)
            .groupBy('album_asset.albumId')
            .execute());
    }
    buildAlbumBaseQuery(ownerId, { isOwned, isShared }) {
        return this.db
            .selectFrom('album')
            .innerJoin('album_user', (join) => join.onRef('album_user.albumId', '=', 'album.id').on('album_user.userId', '=', ownerId))
            .where('album.deletedAt', 'is', null)
            .$if(isOwned === true, (qb) => qb.where('album_user.role', '=', kysely_1.sql.lit(enum_1.AlbumUserRole.Owner)))
            .$if(isOwned === false, (qb) => qb.where('album_user.role', '!=', kysely_1.sql.lit(enum_1.AlbumUserRole.Owner)))
            .$if(isShared !== undefined, (qb) => qb.where((eb) => {
            const isSharedAlbum = eb.or([
                eb.exists(eb
                    .selectFrom('album_user as au')
                    .whereRef('au.albumId', '=', 'album.id')
                    .where('au.role', '!=', kysely_1.sql.lit(enum_1.AlbumUserRole.Owner))),
                eb.exists(eb.selectFrom('shared_link').whereRef('shared_link.albumId', '=', 'album.id')),
            ]);
            return isShared ? isSharedAlbum : eb.not(isSharedAlbum);
        }));
    }
    getAll(ownerId, options = {}) {
        return this.buildAlbumBaseQuery(ownerId, options)
            .selectAll('album')
            .select(withAlbumUsers(ownerId))
            .select(withSharedLink)
            .$if(!!options.id, (qb) => qb.where('album.id', '=', options.id))
            .$if(!!options.name, (qb) => qb.where('album.albumName', '=', options.name))
            .orderBy('album.createdAt', 'desc')
            .execute();
    }
    async getAllIds(ownerId, options = {}) {
        const rows = await this.buildAlbumBaseQuery(ownerId, options)
            .select('album.id')
            .orderBy('album.createdAt', 'desc')
            .execute();
        return rows.map((r) => r.id);
    }
    async restoreAll(userId) {
        await this.db.updateTable('album').set({ deletedAt: null }).where(isAlbumOwned(userId)).execute();
    }
    async softDeleteAll(userId) {
        await this.db.updateTable('album').set({ deletedAt: new Date() }).where(isAlbumOwned(userId)).execute();
    }
    async deleteAll(userId) {
        await this.db.deleteFrom('album').where(isAlbumOwned(userId)).execute();
    }
    async removeAssetsFromAll(assetIds) {
        await this.db.deleteFrom('album_asset').where('album_asset.assetId', 'in', assetIds).execute();
    }
    async removeAssetIds(albumId, assetIds) {
        if (assetIds.length === 0) {
            return;
        }
        await this.db
            .deleteFrom('album_asset')
            .where('album_asset.albumId', '=', albumId)
            .where('album_asset.assetId', 'in', assetIds)
            .execute();
    }
    async getAssetIds(albumId, assetIds) {
        if (assetIds.length === 0) {
            return new Set();
        }
        return this.db
            .selectFrom('album_asset')
            .selectAll()
            .where('album_asset.albumId', '=', albumId)
            .where('album_asset.assetId', 'in', assetIds)
            .execute()
            .then((results) => new Set(results.map(({ assetId }) => assetId)));
    }
    async addAssetIds(albumId, assetIds) {
        if (assetIds.length === 0) {
            return;
        }
        await this.db
            .insertInto('album_asset')
            .expression((eb) => eb.selectFrom(database_2.dummy).select([(0, database_2.asUuid)(albumId).as('albumId'), (0, kysely_1.sql) `unnest(${assetIds}::uuid[])`.as('assetId')]))
            .onConflict((oc) => oc.doNothing())
            .execute();
    }
    async create(album, assetIds, albumUsers, authUserId) {
        if (albumUsers.every((u) => u.role !== enum_1.AlbumUserRole.Owner)) {
            throw new Error('Album must have an owner');
        }
        const userIds = albumUsers.map((u) => u.userId);
        const roles = albumUsers.map((u) => u.role);
        const result = await this.db
            .with('album', (db) => db.insertInto('album').values(album).returningAll())
            .with('album_user', (db) => db
            .insertInto('album_user')
            .expression((eb) => eb
            .selectFrom('album')
            .select(({ ref }) => [
            ref('album.id').as('albumId'),
            (0, kysely_1.sql) `unnest(${userIds}::uuid[])`.as('userId'),
            (0, kysely_1.sql) `unnest(${roles}::album_user_role_enum[])`.as('role'),
        ]))
            .returning(['album_user.albumId', 'album_user.userId', 'album_user.role']))
            .with('album_asset', (db) => db
            .insertInto('album_asset')
            .expression((eb) => eb
            .selectFrom('album')
            .select(({ ref }) => [ref('album.id').as('albumId'), (0, kysely_1.sql) `unnest(${assetIds}::uuid[])`.as('assetId')]))
            .onConflict((oc) => oc.doNothing())
            .returning(['album_asset.albumId', 'album_asset.assetId']))
            .selectFrom('album')
            .selectAll('album')
            .select(withAlbumUsers(authUserId))
            .select(withAssets)
            .$narrowType()
            .executeTakeFirstOrThrow();
        return result;
    }
    update(id, album, authUserId) {
        return this.db
            .updateTable('album')
            .set(album)
            .where('album.id', '=', id)
            .returningAll('album')
            .returning(withSharedLink)
            .returning(withAlbumUsers(authUserId))
            .executeTakeFirstOrThrow();
    }
    async delete(id) {
        await this.db.deleteFrom('album').where('id', '=', id).execute();
    }
    async addAssetIdsToAlbums(values) {
        if (values.length === 0) {
            return;
        }
        await this.db
            .insertInto('album_asset')
            .values(values)
            .onConflict((oc) => oc.columns(['albumId', 'assetId']).doNothing())
            .execute();
    }
    async updateThumbnails() {
        const result = await this.db
            .updateTable('album')
            .set((eb) => ({
            albumThumbnailAssetId: this.updateThumbnailBuilder(eb)
                .select('album_asset.assetId')
                .orderBy('asset.fileCreatedAt', 'desc')
                .limit(kysely_1.sql.lit(1)),
        }))
            .where((eb) => eb.or([
            eb.and([
                eb('albumThumbnailAssetId', 'is', null),
                eb.exists(this.updateThumbnailBuilder(eb).select((0, kysely_1.sql) `1`.as('1'))),
            ]),
            eb.and([
                eb('albumThumbnailAssetId', 'is not', null),
                eb.not(eb.exists(this.updateThumbnailBuilder(eb)
                    .select((0, kysely_1.sql) `1`.as('1'))
                    .whereRef('album.albumThumbnailAssetId', '=', 'album_asset.assetId'))),
            ]),
        ]))
            .execute();
        return Number(result[0].numUpdatedRows);
    }
    updateThumbnailBuilder(eb) {
        return eb
            .selectFrom('album_asset')
            .innerJoin('asset', (join) => join.onRef('album_asset.assetId', '=', 'asset.id').on('asset.deletedAt', 'is', null))
            .whereRef('album_asset.albumId', '=', 'album.id');
    }
    getContributorCounts(id) {
        return this.db
            .selectFrom('album_asset')
            .innerJoin('asset', 'asset.id', 'assetId')
            .where('asset.deletedAt', 'is', kysely_1.sql.lit(null))
            .where('album_asset.albumId', '=', id)
            .select('asset.ownerId as userId')
            .select((eb) => eb.fn.countAll().as('assetCount'))
            .groupBy('asset.ownerId')
            .orderBy('assetCount', 'desc')
            .execute();
    }
    async copyAlbums({ sourceAssetId, targetAssetId }) {
        return this.db
            .insertInto('album_asset')
            .expression((eb) => eb
            .selectFrom('album_asset')
            .select((eb) => ['album_asset.albumId', eb.val(targetAssetId).as('assetId')])
            .where('album_asset.assetId', '=', sourceAssetId))
            .onConflict((oc) => oc.doNothing())
            .execute();
    }
};
exports.AlbumRepository = AlbumRepository;
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.UUID, { withAssets: true }, decorators_1.DummyValue.UUID] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object, String]),
    __metadata("design:returntype", void 0)
], AlbumRepository.prototype, "getById", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.UUID, decorators_1.DummyValue.UUID] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", void 0)
], AlbumRepository.prototype, "getByAssetId", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.UUID, [decorators_1.DummyValue.UUID]] }),
    (0, decorators_1.ChunkedSet)({ paramIndex: 1 }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Array]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "getByAssetIds", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [[decorators_1.DummyValue.UUID]] }),
    (0, decorators_1.ChunkedArray)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "getMetadataForIds", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.UUID, { isOwned: true, isShared: true }] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "getAll", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.UUID, { isOwned: true, isShared: true }] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "getAllIds", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [[decorators_1.DummyValue.UUID]] }),
    (0, decorators_1.Chunked)(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "removeAssetsFromAll", null);
__decorate([
    (0, decorators_1.Chunked)({ paramIndex: 1 }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Array]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "removeAssetIds", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.UUID, [decorators_1.DummyValue.UUID]] }),
    (0, decorators_1.ChunkedSet)({ paramIndex: 1 }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Array]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "getAssetIds", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.UUID, [decorators_1.DummyValue.UUID]] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Array]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "addAssetIds", null);
__decorate([
    (0, decorators_1.GenerateSql)({
        params: [
            { albumName: decorators_1.DummyValue.STRING },
            [],
            [{ userId: decorators_1.DummyValue.UUID, role: enum_1.AlbumUserRole.Owner }, decorators_1.DummyValue.UUID],
        ],
    }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Array, Array, String]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "create", null);
__decorate([
    (0, decorators_1.Chunked)({ chunkSize: 30_000 }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "addAssetIdsToAlbums", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.UUID] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", void 0)
], AlbumRepository.prototype, "getContributorCounts", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [{ sourceAssetId: decorators_1.DummyValue.UUID, targetAssetId: decorators_1.DummyValue.UUID }] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], AlbumRepository.prototype, "copyAlbums", null);
exports.AlbumRepository = AlbumRepository = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, nestjs_kysely_1.InjectKysely)()),
    __metadata("design:paramtypes", [kysely_1.Kysely])
], AlbumRepository);
//# sourceMappingURL=album.repository.js.map