"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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SearchService = void 0;
const common_1 = require("@nestjs/common");
const mnemonist_1 = require("mnemonist");
const asset_response_dto_1 = require("../dtos/asset-response.dto");
const person_dto_1 = require("../dtos/person.dto");
const search_dto_1 = require("../dtos/search.dto");
const enum_1 = require("../enum");
const base_service_1 = require("./base.service");
const access_1 = require("../utils/access");
const asset_util_1 = require("../utils/asset.util");
const misc_1 = require("../utils/misc");
let SearchService = class SearchService extends base_service_1.BaseService {
    embeddingCache = new mnemonist_1.LRUMap(100);
    async searchPerson(auth, dto) {
        const people = await this.personRepository.getByName(auth.user.id, dto.name, { withHidden: dto.withHidden });
        return people.map((person) => (0, person_dto_1.mapPerson)(person));
    }
    async searchPlaces(dto) {
        const places = await this.searchRepository.searchPlaces(dto.name);
        return places.map((place) => (0, search_dto_1.mapPlaces)(place));
    }
    async getExploreData(auth) {
        const options = { maxFields: 12, minAssetsPerField: 5 };
        const cities = await this.assetRepository.getAssetIdByCity(auth.user.id, options);
        const cityAssets = await this.assetRepository.getByIdsWithAllRelationsButStacks(cities.items.map(({ data }) => data));
        const cityItems = cityAssets.map((asset) => ({ value: asset.exifInfo.city, data: (0, asset_response_dto_1.mapAsset)(asset, { auth }) }));
        const recents = await this.assetRepository.getRecentlyCreatedAssetIds(auth.user.id, options.maxFields);
        const recentAssets = await this.assetRepository.getByIdsWithAllRelationsButStacks(recents.items.map((item) => item.data));
        const recentItems = recentAssets.map((asset) => ({
            value: asset.createdAt.toISOString(),
            data: (0, asset_response_dto_1.mapAsset)(asset, { auth }),
        }));
        return [
            { fieldName: cities.fieldName, items: cityItems },
            { fieldName: recents.fieldName, items: recentItems },
        ];
    }
    async searchMetadata(auth, dto) {
        if (dto.visibility === enum_1.AssetVisibility.Locked) {
            (0, access_1.requireElevatedPermission)(auth);
        }
        let checksum;
        if (dto.checksum) {
            const encoding = dto.checksum.length === 28 ? 'base64' : 'hex';
            checksum = Buffer.from(dto.checksum, encoding);
        }
        let userIds;
        if (dto.albumIds && dto.albumIds.length > 0) {
            await this.requireAccess({ auth, ids: dto.albumIds, permission: enum_1.Permission.AlbumRead });
        }
        else if (auth.sharedLink) {
            throw new common_1.BadRequestException('Shared link access is only allowed in combination with an albumIds filter');
        }
        else {
            userIds = await this.getUserIdsToSearch(auth, dto.visibility);
        }
        const page = dto.page ?? 1;
        const size = dto.size || 250;
        const { hasNextPage, items } = await this.searchRepository.searchMetadata({ page, size }, {
            ...dto,
            checksum,
            visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
            userIds,
            orderDirection: dto.order ?? enum_1.AssetOrder.Desc,
        });
        return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth });
    }
    async searchStatistics(auth, dto) {
        const userIds = await this.getUserIdsToSearch(auth, dto.visibility);
        if (dto.visibility === enum_1.AssetVisibility.Locked) {
            (0, access_1.requireElevatedPermission)(auth);
        }
        return await this.searchRepository.searchStatistics({
            ...dto,
            visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
            userIds,
        });
    }
    async searchRandom(auth, dto) {
        if (dto.visibility === enum_1.AssetVisibility.Locked) {
            (0, access_1.requireElevatedPermission)(auth);
        }
        const userIds = await this.getUserIdsToSearch(auth, dto.visibility);
        const items = await this.searchRepository.searchRandom(dto.size || 250, {
            ...dto,
            visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
            userIds,
        });
        return items.map((item) => (0, asset_response_dto_1.mapAsset)(item, { auth }));
    }
    async searchLargeAssets(auth, dto) {
        if (dto.visibility === enum_1.AssetVisibility.Locked) {
            (0, access_1.requireElevatedPermission)(auth);
        }
        const userIds = await this.getUserIdsToSearch(auth, dto.visibility);
        const items = await this.searchRepository.searchLargeAssets(dto.size || 250, {
            ...dto,
            visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
            userIds,
        });
        return items.map((item) => (0, asset_response_dto_1.mapAsset)(item, { auth }));
    }
    async searchSmart(auth, dto) {
        if (dto.visibility === enum_1.AssetVisibility.Locked) {
            (0, access_1.requireElevatedPermission)(auth);
        }
        const { machineLearning } = await this.getConfig({ withCache: false });
        if (!(0, misc_1.isSmartSearchEnabled)(machineLearning)) {
            throw new common_1.BadRequestException('Smart search is not enabled');
        }
        const userIds = this.getUserIdsToSearch(auth, dto.visibility);
        let embedding;
        if (dto.query) {
            const key = machineLearning.clip.modelName + dto.query + dto.language;
            embedding = this.embeddingCache.get(key);
            if (!embedding) {
                embedding = await this.machineLearningRepository.encodeText(dto.query, {
                    modelName: machineLearning.clip.modelName,
                    language: dto.language,
                });
                this.embeddingCache.set(key, embedding);
            }
        }
        else if (dto.queryAssetId) {
            await this.requireAccess({ auth, permission: enum_1.Permission.AssetRead, ids: [dto.queryAssetId] });
            const getEmbeddingResponse = await this.searchRepository.getEmbedding(dto.queryAssetId);
            const assetEmbedding = getEmbeddingResponse?.embedding;
            if (!assetEmbedding) {
                throw new common_1.BadRequestException(`Asset ${dto.queryAssetId} has no embedding`);
            }
            embedding = assetEmbedding;
        }
        else {
            throw new common_1.BadRequestException('Either `query` or `queryAssetId` must be set');
        }
        const page = dto.page ?? 1;
        const size = dto.size || 100;
        const { hasNextPage, items } = await this.searchRepository.searchSmart({ page, size }, {
            ...dto,
            userIds: await userIds,
            embedding,
            visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
        });
        return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth });
    }
    async getAssetsByCity(auth) {
        const userIds = await this.getUserIdsToSearch(auth);
        const assets = await this.searchRepository.getAssetsByCity(userIds);
        return assets.map((asset) => (0, asset_response_dto_1.mapAsset)(asset));
    }
    async getSearchSuggestions(auth, dto) {
        const userIds = await this.getUserIdsToSearch(auth);
        const suggestions = await this.getSuggestions(userIds, dto);
        if (dto.includeNull) {
            suggestions.push(null);
        }
        return suggestions;
    }
    getSuggestions(userIds, dto) {
        switch (dto.type) {
            case search_dto_1.SearchSuggestionType.COUNTRY: {
                return this.searchRepository.getCountries(userIds);
            }
            case search_dto_1.SearchSuggestionType.STATE: {
                return this.searchRepository.getStates(userIds, dto);
            }
            case search_dto_1.SearchSuggestionType.CITY: {
                return this.searchRepository.getCities(userIds, dto);
            }
            case search_dto_1.SearchSuggestionType.CAMERA_MAKE: {
                return this.searchRepository.getCameraMakes(userIds, dto);
            }
            case search_dto_1.SearchSuggestionType.CAMERA_MODEL: {
                return this.searchRepository.getCameraModels(userIds, dto);
            }
            case search_dto_1.SearchSuggestionType.CAMERA_LENS_MODEL: {
                return this.searchRepository.getCameraLensModels(userIds, dto);
            }
            default: {
                return Promise.resolve([]);
            }
        }
    }
    async getUserIdsToSearch(auth, visibility) {
        if (visibility === enum_1.AssetVisibility.Locked) {
            return [auth.user.id];
        }
        const partnerIds = await (0, asset_util_1.getMyPartnerIds)({
            userId: auth.user.id,
            repository: this.partnerRepository,
            timelineEnabled: true,
        });
        return [auth.user.id, ...partnerIds];
    }
    mapResponse(assets, nextPage, options) {
        return {
            albums: { total: 0, count: 0, items: [], facets: [] },
            assets: {
                total: assets.length,
                count: assets.length,
                items: assets.map((asset) => (0, asset_response_dto_1.mapAsset)(asset, options)),
                facets: [],
                nextPage,
            },
        };
    }
};
exports.SearchService = SearchService;
exports.SearchService = SearchService = __decorate([
    (0, common_1.Injectable)()
], SearchService);
//# sourceMappingURL=search.service.js.map