"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 MachineLearningRepository_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MachineLearningRepository = exports.ModelType = exports.ModelTask = void 0;
const common_1 = require("@nestjs/common");
const luxon_1 = require("luxon");
const promises_1 = require("node:fs/promises");
const logging_repository_1 = require("./logging.repository");
var ModelTask;
(function (ModelTask) {
    ModelTask["FACIAL_RECOGNITION"] = "facial-recognition";
    ModelTask["SEARCH"] = "clip";
    ModelTask["OCR"] = "ocr";
})(ModelTask || (exports.ModelTask = ModelTask = {}));
var ModelType;
(function (ModelType) {
    ModelType["DETECTION"] = "detection";
    ModelType["PIPELINE"] = "pipeline";
    ModelType["RECOGNITION"] = "recognition";
    ModelType["TEXTUAL"] = "textual";
    ModelType["VISUAL"] = "visual";
    ModelType["OCR"] = "ocr";
})(ModelType || (exports.ModelType = ModelType = {}));
let MachineLearningRepository = MachineLearningRepository_1 = class MachineLearningRepository {
    logger;
    healthyMap = {};
    interval;
    _config;
    get config() {
        if (!this._config) {
            throw new Error('Machine learning repository not been setup');
        }
        return this._config;
    }
    constructor(logger) {
        this.logger = logger;
        this.logger.setContext(MachineLearningRepository_1.name);
    }
    setup(config) {
        this._config = config;
        this.teardown();
        for (const url of Object.keys(this.healthyMap)) {
            if (!config.urls.includes(url)) {
                delete this.healthyMap[url];
            }
        }
        if (!config.enabled || !config.availabilityChecks.enabled) {
            return;
        }
        this.tick();
        this.interval = setInterval(() => this.tick(), luxon_1.Duration.fromObject({ milliseconds: config.availabilityChecks.interval }).as('milliseconds'));
    }
    teardown() {
        if (this.interval) {
            clearInterval(this.interval);
        }
    }
    tick() {
        for (const url of this.config.urls) {
            void this.check(url);
        }
    }
    async check(url) {
        let healthy = false;
        try {
            const response = await fetch(new URL('ping', url), {
                signal: AbortSignal.timeout(this.config.availabilityChecks.timeout),
            });
            if (response.ok) {
                healthy = true;
            }
        }
        catch {
        }
        this.setHealthy(url, healthy);
    }
    setHealthy(url, healthy) {
        if (this.healthyMap[url] !== healthy) {
            this.logger.log(`Machine learning server became ${healthy ? 'healthy' : 'unhealthy'} (${url}).`);
        }
        this.healthyMap[url] = healthy;
    }
    isHealthy(url) {
        if (!this.config.availabilityChecks.enabled) {
            return true;
        }
        return this.healthyMap[url];
    }
    async predict(payload, config) {
        const formData = await this.getFormData(payload, config);
        for (const url of [
            ...this.config.urls.filter((url) => this.isHealthy(url)),
            ...this.config.urls.filter((url) => !this.isHealthy(url)),
        ]) {
            try {
                const response = await fetch(new URL('predict', url), { method: 'POST', body: formData });
                if (response.ok) {
                    this.setHealthy(url, true);
                    return response.json();
                }
                this.logger.warn(`Machine learning request to "${url}" failed with status ${response.status}: ${response.statusText}`);
            }
            catch (error) {
                this.logger.warn(`Machine learning request to "${url}" failed: ${error instanceof Error ? error.message : error}`);
            }
            this.setHealthy(url, false);
        }
        throw new Error(`Machine learning request '${JSON.stringify(config)}' failed for all URLs`);
    }
    async detectFaces(imagePath, { modelName, minScore }) {
        const request = {
            [ModelTask.FACIAL_RECOGNITION]: {
                [ModelType.DETECTION]: { modelName, options: { minScore } },
                [ModelType.RECOGNITION]: { modelName },
            },
        };
        const response = await this.predict({ imagePath }, request);
        return {
            imageHeight: response.imageHeight,
            imageWidth: response.imageWidth,
            faces: response[ModelTask.FACIAL_RECOGNITION],
        };
    }
    async encodeImage(imagePath, { modelName }) {
        const request = { [ModelTask.SEARCH]: { [ModelType.VISUAL]: { modelName } } };
        const response = await this.predict({ imagePath }, request);
        return response[ModelTask.SEARCH];
    }
    async encodeText(text, { language, modelName }) {
        const request = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: { modelName, options: { language } } } };
        const response = await this.predict({ text }, request);
        return response[ModelTask.SEARCH];
    }
    async ocr(imagePath, { modelName, minDetectionScore, minRecognitionScore, maxResolution }) {
        const request = {
            [ModelTask.OCR]: {
                [ModelType.DETECTION]: { modelName, options: { minScore: minDetectionScore, maxResolution } },
                [ModelType.RECOGNITION]: { modelName, options: { minScore: minRecognitionScore } },
            },
        };
        const response = await this.predict({ imagePath }, request);
        return response[ModelTask.OCR];
    }
    async getFormData(payload, config) {
        const formData = new FormData();
        formData.append('entries', JSON.stringify(config));
        if ('imagePath' in payload) {
            const fileBuffer = await (0, promises_1.readFile)(payload.imagePath);
            formData.append('image', new Blob([new Uint8Array(fileBuffer)]));
        }
        else if ('text' in payload) {
            formData.append('text', payload.text);
        }
        else {
            throw new Error('Invalid input');
        }
        return formData;
    }
};
exports.MachineLearningRepository = MachineLearningRepository;
exports.MachineLearningRepository = MachineLearningRepository = MachineLearningRepository_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [logging_repository_1.LoggingRepository])
], MachineLearningRepository);
//# sourceMappingURL=machine-learning.repository.js.map