"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); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SearchRepository = void 0;
const common_1 = require("@nestjs/common");
const kysely_1 = require("kysely");
const nestjs_kysely_1 = require("nestjs-kysely");
const decorators_1 = require("../decorators");
const enum_1 = require("../enum");
const database_repository_1 = require("./database.repository");
const database_1 = require("../utils/database");
const pagination_1 = require("../utils/pagination");
const zod_1 = __importDefault(require("zod"));
let SearchRepository = class SearchRepository {
    db;
    constructor(db) {
        this.db = db;
    }
    async searchMetadata(pagination, options) {
        const orderDirection = (options.orderDirection?.toLowerCase() || 'desc');
        const items = await (0, database_1.searchAssetBuilder)(this.db, options)
            .selectAll('asset')
            .orderBy('asset.fileCreatedAt', orderDirection)
            .limit(pagination.size + 1)
            .offset((pagination.page - 1) * pagination.size)
            .execute();
        return (0, pagination_1.paginationHelper)(items, pagination.size);
    }
    searchStatistics(options) {
        return (0, database_1.searchAssetBuilder)(this.db, options)
            .select((qb) => qb.fn.countAll().as('total'))
            .executeTakeFirstOrThrow();
    }
    async searchRandom(size, options) {
        return (0, database_1.searchAssetBuilder)(this.db, options)
            .selectAll('asset')
            .orderBy((0, kysely_1.sql) `random()`)
            .limit(size)
            .execute();
    }
    searchLargeAssets(size, options) {
        const orderDirection = (options.orderDirection?.toLowerCase() || 'desc');
        return (0, database_1.searchAssetBuilder)(this.db, options)
            .selectAll('asset')
            .$call(database_1.withExifInner)
            .where('asset_exif.fileSizeInByte', '>', options.minFileSize || 0)
            .orderBy('asset_exif.fileSizeInByte', orderDirection)
            .limit(size)
            .execute();
    }
    searchSmart(pagination, options) {
        if (!zod_1.default.int().min(1).max(1000).safeParse(pagination.size).success) {
            throw new Error(`Invalid value for 'size': ${pagination.size}`);
        }
        return this.db.transaction().execute(async (trx) => {
            await (0, kysely_1.sql) `set local vchordrq.probes = ${kysely_1.sql.lit(database_repository_1.probes[enum_1.VectorIndex.Clip])}`.execute(trx);
            const items = await (0, database_1.searchAssetBuilder)(trx, options)
                .selectAll('asset')
                .innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
                .orderBy((0, kysely_1.sql) `smart_search.embedding <=> ${options.embedding}`)
                .limit(pagination.size + 1)
                .offset((pagination.page - 1) * pagination.size)
                .execute();
            return (0, pagination_1.paginationHelper)(items, pagination.size);
        });
    }
    async getEmbedding(assetId) {
        return this.db.selectFrom('smart_search').selectAll().where('assetId', '=', assetId).executeTakeFirst();
    }
    searchFaces({ userIds, embedding, numResults, maxDistance, hasPerson, minBirthDate }) {
        if (!zod_1.default.int().min(1).max(1000).safeParse(numResults).success) {
            throw new Error(`Invalid value for 'numResults': ${numResults}`);
        }
        return this.db.transaction().execute(async (trx) => {
            await (0, kysely_1.sql) `set local vchordrq.probes = ${kysely_1.sql.lit(database_repository_1.probes[enum_1.VectorIndex.Face])}`.execute(trx);
            return await trx
                .with('cte', (qb) => qb
                .selectFrom('asset_face')
                .select([
                'asset_face.id',
                'asset_face.personId',
                (0, kysely_1.sql) `face_search.embedding <=> ${embedding}`.as('distance'),
            ])
                .innerJoin('asset', 'asset.id', 'asset_face.assetId')
                .innerJoin('face_search', 'face_search.faceId', 'asset_face.id')
                .leftJoin('person', 'person.id', 'asset_face.personId')
                .where('asset.ownerId', '=', (0, database_1.anyUuid)(userIds))
                .where('asset.deletedAt', 'is', null)
                .$if(!!hasPerson, (qb) => qb.where('asset_face.personId', 'is not', null))
                .$if(!!minBirthDate, (qb) => qb.where((eb) => eb.or([eb('person.birthDate', 'is', null), eb('person.birthDate', '<=', minBirthDate)])))
                .orderBy('distance')
                .limit(numResults))
                .selectFrom('cte')
                .selectAll()
                .where('cte.distance', '<=', maxDistance)
                .execute();
        });
    }
    searchPlaces(placeName) {
        return this.db
            .selectFrom('geodata_places')
            .selectAll()
            .where(() => (0, kysely_1.sql) `
            f_unaccent(name) %>> f_unaccent(${placeName}) or
            f_unaccent("admin2Name") %>> f_unaccent(${placeName}) or
            f_unaccent("admin1Name") %>> f_unaccent(${placeName}) or
            f_unaccent("alternateNames") %>> f_unaccent(${placeName})
          `)
            .orderBy((0, kysely_1.sql) `
          coalesce(f_unaccent(name) <->>> f_unaccent(${placeName}), 0.1) +
          coalesce(f_unaccent("admin2Name") <->>> f_unaccent(${placeName}), 0.1) +
          coalesce(f_unaccent("admin1Name") <->>> f_unaccent(${placeName}), 0.1) +
          coalesce(f_unaccent("alternateNames") <->>> f_unaccent(${placeName}), 0.1)
        `)
            .limit(20)
            .execute();
    }
    getAssetsByCity(userIds) {
        return this.db
            .withRecursive('cte', (qb) => {
            const base = qb
                .selectFrom('asset_exif')
                .select(['city', 'assetId'])
                .innerJoin('asset', 'asset.id', 'asset_exif.assetId')
                .where('asset.ownerId', '=', (0, database_1.anyUuid)(userIds))
                .where('asset.visibility', '=', enum_1.AssetVisibility.Timeline)
                .where('asset.type', '=', enum_1.AssetType.Image)
                .where('asset.deletedAt', 'is', null)
                .orderBy('city')
                .limit(1);
            const recursive = qb
                .selectFrom('cte')
                .select(['l.city', 'l.assetId'])
                .innerJoinLateral((qb) => qb
                .selectFrom('asset_exif')
                .select(['city', 'assetId'])
                .innerJoin('asset', 'asset.id', 'asset_exif.assetId')
                .where('asset.ownerId', '=', (0, database_1.anyUuid)(userIds))
                .where('asset.visibility', '=', enum_1.AssetVisibility.Timeline)
                .where('asset.type', '=', enum_1.AssetType.Image)
                .where('asset.deletedAt', 'is', null)
                .whereRef('asset_exif.city', '>', 'cte.city')
                .orderBy('city')
                .limit(1)
                .as('l'), (join) => join.onTrue());
            return (0, kysely_1.sql) `(${base} union all ${recursive})`;
        })
            .selectFrom('asset')
            .innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
            .innerJoin('cte', 'asset.id', 'cte.assetId')
            .selectAll('asset')
            .select((eb) => eb
            .fn('to_jsonb', [eb.table('asset_exif')])
            .$castTo()
            .as('exifInfo'))
            .orderBy('asset_exif.city')
            .execute();
    }
    async upsert(assetId, embedding) {
        await this.db
            .insertInto('smart_search')
            .values({ assetId, embedding })
            .onConflict((oc) => oc.column('assetId').doUpdateSet((eb) => ({ embedding: eb.ref('excluded.embedding') })))
            .execute();
    }
    async getCountries(userIds) {
        const res = await this.getExifField('country', userIds).execute();
        return res.map((row) => row.country);
    }
    async getStates(userIds, { country }) {
        const res = await this.getExifField('state', userIds)
            .$if(!!country, (qb) => qb.where('country', '=', country))
            .execute();
        return res.map((row) => row.state);
    }
    async getCities(userIds, { country, state }) {
        const res = await this.getExifField('city', userIds)
            .$if(!!country, (qb) => qb.where('country', '=', country))
            .$if(!!state, (qb) => qb.where('state', '=', state))
            .execute();
        return res.map((row) => row.city);
    }
    async getCameraMakes(userIds, { model, lensModel }) {
        const res = await this.getExifField('make', userIds)
            .$if(!!model, (qb) => qb.where('model', '=', model))
            .$if(!!lensModel, (qb) => qb.where('lensModel', '=', lensModel))
            .execute();
        return res.map((row) => row.make);
    }
    async getCameraModels(userIds, { make, lensModel }) {
        const res = await this.getExifField('model', userIds)
            .$if(!!make, (qb) => qb.where('make', '=', make))
            .$if(!!lensModel, (qb) => qb.where('lensModel', '=', lensModel))
            .execute();
        return res.map((row) => row.model);
    }
    async getCameraLensModels(userIds, { make, model }) {
        const res = await this.getExifField('lensModel', userIds)
            .$if(!!make, (qb) => qb.where('make', '=', make))
            .$if(!!model, (qb) => qb.where('model', '=', model))
            .execute();
        return res.map((row) => row.lensModel);
    }
    getExifField(field, userIds) {
        return this.db
            .selectFrom('asset_exif')
            .select(field)
            .distinctOn(field)
            .innerJoin('asset', 'asset.id', 'asset_exif.assetId')
            .where('ownerId', '=', (0, database_1.anyUuid)(userIds))
            .where('visibility', '=', enum_1.AssetVisibility.Timeline)
            .where('deletedAt', 'is', null)
            .where(field, 'is not', null)
            .where(field, '!=', '');
    }
};
exports.SearchRepository = SearchRepository;
__decorate([
    (0, decorators_1.GenerateSql)({
        params: [
            { page: 1, size: 100 },
            {
                takenAfter: decorators_1.DummyValue.DATE,
                lensModel: decorators_1.DummyValue.STRING,
                withStacked: true,
                isFavorite: true,
                userIds: [decorators_1.DummyValue.UUID],
            },
        ],
    }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", Promise)
], SearchRepository.prototype, "searchMetadata", null);
__decorate([
    (0, decorators_1.GenerateSql)({
        params: [
            {
                takenAfter: decorators_1.DummyValue.DATE,
                lensModel: decorators_1.DummyValue.STRING,
                isFavorite: true,
                userIds: [decorators_1.DummyValue.UUID],
            },
        ],
    }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], SearchRepository.prototype, "searchStatistics", null);
__decorate([
    (0, decorators_1.GenerateSql)({
        params: [
            100,
            {
                takenAfter: decorators_1.DummyValue.DATE,
                lensModel: decorators_1.DummyValue.STRING,
                withStacked: true,
                isFavorite: true,
                userIds: [decorators_1.DummyValue.UUID],
            },
        ],
    }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Object]),
    __metadata("design:returntype", Promise)
], SearchRepository.prototype, "searchRandom", null);
__decorate([
    (0, decorators_1.GenerateSql)({
        params: [
            100,
            {
                takenAfter: decorators_1.DummyValue.DATE,
                lensModel: decorators_1.DummyValue.STRING,
                withStacked: true,
                isFavorite: true,
                userIds: [decorators_1.DummyValue.UUID],
            },
        ],
    }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Number, Object]),
    __metadata("design:returntype", void 0)
], SearchRepository.prototype, "searchLargeAssets", null);
__decorate([
    (0, decorators_1.GenerateSql)({
        params: [
            { page: 1, size: 200 },
            {
                takenAfter: decorators_1.DummyValue.DATE,
                embedding: decorators_1.DummyValue.VECTOR,
                lensModel: decorators_1.DummyValue.STRING,
                withStacked: true,
                isFavorite: true,
                userIds: [decorators_1.DummyValue.UUID],
            },
        ],
    }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", void 0)
], SearchRepository.prototype, "searchSmart", null);
__decorate([
    (0, decorators_1.GenerateSql)({
        params: [decorators_1.DummyValue.UUID],
    }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], SearchRepository.prototype, "getEmbedding", null);
__decorate([
    (0, decorators_1.GenerateSql)({
        params: [
            {
                userIds: [decorators_1.DummyValue.UUID],
                embedding: decorators_1.DummyValue.VECTOR,
                numResults: 10,
                maxDistance: 0.6,
            },
        ],
    }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], SearchRepository.prototype, "searchFaces", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [decorators_1.DummyValue.STRING] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", void 0)
], SearchRepository.prototype, "searchPlaces", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [[decorators_1.DummyValue.UUID]] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array]),
    __metadata("design:returntype", void 0)
], SearchRepository.prototype, "getAssetsByCity", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [[decorators_1.DummyValue.UUID], decorators_1.DummyValue.STRING] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], SearchRepository.prototype, "getStates", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [[decorators_1.DummyValue.UUID], decorators_1.DummyValue.STRING, decorators_1.DummyValue.STRING] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], SearchRepository.prototype, "getCities", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [[decorators_1.DummyValue.UUID], decorators_1.DummyValue.STRING, decorators_1.DummyValue.STRING] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], SearchRepository.prototype, "getCameraMakes", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [[decorators_1.DummyValue.UUID], decorators_1.DummyValue.STRING, decorators_1.DummyValue.STRING] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], SearchRepository.prototype, "getCameraModels", null);
__decorate([
    (0, decorators_1.GenerateSql)({ params: [[decorators_1.DummyValue.UUID], decorators_1.DummyValue.STRING] }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], SearchRepository.prototype, "getCameraLensModels", null);
exports.SearchRepository = SearchRepository = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, nestjs_kysely_1.InjectKysely)()),
    __metadata("design:paramtypes", [kysely_1.Kysely])
], SearchRepository);
//# sourceMappingURL=search.repository.js.map