"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.PluginService = void 0;
const extism_1 = require("@extism/extism");
const common_1 = require("@nestjs/common");
const class_transformer_1 = require("class-transformer");
const class_validator_1 = require("class-validator");
const node_path_1 = require("node:path");
const decorators_1 = require("../decorators");
const plugin_manifest_dto_1 = require("../dtos/plugin-manifest.dto");
const plugin_dto_1 = require("../dtos/plugin.dto");
const enum_1 = require("../enum");
const plugins_1 = require("../plugins");
const base_service_1 = require("./base.service");
const plugin_host_functions_1 = require("./plugin-host.functions");
let PluginService = class PluginService extends base_service_1.BaseService {
    pluginJwtSecret;
    loadedPlugins = new Map();
    hostFunctions;
    async onBootstrap() {
        this.pluginJwtSecret = this.cryptoRepository.randomBytesAsText(32);
        await this.loadPluginsFromManifests();
        this.hostFunctions = new plugin_host_functions_1.PluginHostFunctions(this.assetRepository, this.albumRepository, this.accessRepository, this.cryptoRepository, this.logger, this.pluginJwtSecret);
        await this.loadPlugins();
    }
    getTriggers() {
        return plugins_1.pluginTriggers;
    }
    async getAll() {
        const plugins = await this.pluginRepository.getAllPlugins();
        return plugins.map((plugin) => (0, plugin_dto_1.mapPlugin)(plugin));
    }
    async get(id) {
        const plugin = await this.pluginRepository.getPlugin(id);
        if (!plugin) {
            throw new common_1.BadRequestException('Plugin not found');
        }
        return (0, plugin_dto_1.mapPlugin)(plugin);
    }
    async loadPluginsFromManifests() {
        const { resourcePaths, plugins } = this.configRepository.getEnv();
        const coreManifestPath = `${resourcePaths.corePlugin}/manifest.json`;
        const coreManifest = await this.readAndValidateManifest(coreManifestPath);
        await this.loadPluginToDatabase(coreManifest, resourcePaths.corePlugin);
        this.logger.log(`Successfully processed core plugin: ${coreManifest.name} (version ${coreManifest.version})`);
        if (plugins.external.allow && plugins.external.installFolder) {
            await this.loadExternalPlugins(plugins.external.installFolder);
        }
    }
    async loadExternalPlugins(installFolder) {
        try {
            const entries = await this.pluginRepository.readDirectory(installFolder);
            for (const entry of entries) {
                if (!entry.isDirectory()) {
                    continue;
                }
                const pluginFolder = (0, node_path_1.join)(installFolder, entry.name);
                const manifestPath = (0, node_path_1.join)(pluginFolder, 'manifest.json');
                try {
                    const manifest = await this.readAndValidateManifest(manifestPath);
                    await this.loadPluginToDatabase(manifest, pluginFolder);
                    this.logger.log(`Successfully processed external plugin: ${manifest.name} (version ${manifest.version})`);
                }
                catch (error) {
                    this.logger.warn(`Failed to load external plugin from ${manifestPath}:`, error);
                }
            }
        }
        catch (error) {
            this.logger.error(`Failed to scan external plugins folder ${installFolder}:`, error);
        }
    }
    async loadPluginToDatabase(manifest, basePath) {
        const currentPlugin = await this.pluginRepository.getPluginByName(manifest.name);
        if (currentPlugin != null && currentPlugin.version === manifest.version) {
            this.logger.log(`Plugin ${manifest.name} is up to date (version ${manifest.version}). Skipping`);
            return;
        }
        const { plugin, filters, actions } = await this.pluginRepository.loadPlugin(manifest, basePath);
        this.logger.log(`Upserted plugin: ${plugin.name} (ID: ${plugin.id}, version: ${plugin.version})`);
        for (const filter of filters) {
            this.logger.log(`Upserted plugin filter: ${filter.methodName} (ID: ${filter.id})`);
        }
        for (const action of actions) {
            this.logger.log(`Upserted plugin action: ${action.methodName} (ID: ${action.id})`);
        }
    }
    async readAndValidateManifest(manifestPath) {
        const content = await this.storageRepository.readTextFile(manifestPath);
        const manifestData = JSON.parse(content);
        const manifest = (0, class_transformer_1.plainToInstance)(plugin_manifest_dto_1.PluginManifestDto, manifestData);
        await (0, class_validator_1.validateOrReject)(manifest, {
            whitelist: true,
            forbidNonWhitelisted: true,
        });
        return manifest;
    }
    async loadPlugins() {
        const plugins = await this.pluginRepository.getAllPlugins();
        for (const plugin of plugins) {
            try {
                this.logger.debug(`Loading plugin: ${plugin.name} from ${plugin.wasmPath}`);
                const extismPlugin = await (0, extism_1.newPlugin)(plugin.wasmPath, {
                    useWasi: true,
                    functions: this.hostFunctions.getHostFunctions(),
                });
                this.loadedPlugins.set(plugin.id, extismPlugin);
                this.logger.log(`Successfully loaded plugin: ${plugin.name}`);
            }
            catch (error) {
                this.logger.error(`Failed to load plugin ${plugin.name}:`, error);
            }
        }
    }
    async handleAssetCreate({ asset }) {
        await this.handleTrigger(enum_1.PluginTriggerType.AssetCreate, {
            ownerId: asset.ownerId,
            event: { userId: asset.ownerId, asset },
        });
    }
    async handleTrigger(triggerType, params) {
        const workflows = await this.workflowRepository.getWorkflowByOwnerAndTrigger(params.ownerId, triggerType);
        if (workflows.length === 0) {
            return;
        }
        const jobs = workflows.map((workflow) => ({
            name: enum_1.JobName.WorkflowRun,
            data: {
                id: workflow.id,
                type: triggerType,
                event: params.event,
            },
        }));
        await this.jobRepository.queueAll(jobs);
        this.logger.debug(`Queued ${jobs.length} workflow execution jobs for trigger ${triggerType}`);
    }
    async handleWorkflowRun({ id: workflowId, type, event }) {
        try {
            const workflow = await this.workflowRepository.getWorkflow(workflowId);
            if (!workflow) {
                this.logger.error(`Workflow ${workflowId} not found`);
                return enum_1.JobStatus.Failed;
            }
            const workflowFilters = await this.workflowRepository.getFilters(workflowId);
            const workflowActions = await this.workflowRepository.getActions(workflowId);
            switch (type) {
                case enum_1.PluginTriggerType.AssetCreate: {
                    const data = event;
                    const asset = data.asset;
                    const authToken = this.cryptoRepository.signJwt({ userId: data.userId }, this.pluginJwtSecret);
                    const context = {
                        authToken,
                        asset,
                    };
                    const filtersPassed = await this.executeFilters(workflowFilters, context);
                    if (!filtersPassed) {
                        return enum_1.JobStatus.Skipped;
                    }
                    await this.executeActions(workflowActions, context);
                    this.logger.debug(`Workflow ${workflowId} executed successfully`);
                    return enum_1.JobStatus.Success;
                }
                case enum_1.PluginTriggerType.PersonRecognized: {
                    this.logger.error('unimplemented');
                    return enum_1.JobStatus.Skipped;
                }
                default: {
                    this.logger.error(`Unknown workflow trigger type: ${type}`);
                    return enum_1.JobStatus.Failed;
                }
            }
        }
        catch (error) {
            this.logger.error(`Error executing workflow ${workflowId}:`, error);
            return enum_1.JobStatus.Failed;
        }
    }
    async executeFilters(workflowFilters, context) {
        for (const workflowFilter of workflowFilters) {
            const filter = await this.pluginRepository.getFilter(workflowFilter.pluginFilterId);
            if (!filter) {
                this.logger.error(`Filter ${workflowFilter.pluginFilterId} not found`);
                return false;
            }
            const pluginInstance = this.loadedPlugins.get(filter.pluginId);
            if (!pluginInstance) {
                this.logger.error(`Plugin ${filter.pluginId} not loaded`);
                return false;
            }
            const filterInput = {
                authToken: context.authToken,
                config: workflowFilter.filterConfig,
                data: {
                    asset: context.asset,
                },
            };
            this.logger.debug(`Calling filter ${filter.methodName} with input: ${JSON.stringify(filterInput)}`);
            const filterResult = await pluginInstance.call(filter.methodName, new TextEncoder().encode(JSON.stringify(filterInput)));
            if (!filterResult) {
                this.logger.error(`Filter ${filter.methodName} returned null`);
                return false;
            }
            const result = JSON.parse(filterResult.text());
            if (result.passed === false) {
                this.logger.debug(`Filter ${filter.methodName} returned false, stopping workflow execution`);
                return false;
            }
        }
        return true;
    }
    async executeActions(workflowActions, context) {
        for (const workflowAction of workflowActions) {
            const action = await this.pluginRepository.getAction(workflowAction.pluginActionId);
            if (!action) {
                throw new Error(`Action ${workflowAction.pluginActionId} not found`);
            }
            const pluginInstance = this.loadedPlugins.get(action.pluginId);
            if (!pluginInstance) {
                throw new Error(`Plugin ${action.pluginId} not loaded`);
            }
            const actionInput = {
                authToken: context.authToken,
                config: workflowAction.actionConfig,
                data: {
                    asset: context.asset,
                },
            };
            this.logger.debug(`Calling action ${action.methodName} with input: ${JSON.stringify(actionInput)}`);
            await pluginInstance.call(action.methodName, JSON.stringify(actionInput));
        }
    }
};
exports.PluginService = PluginService;
__decorate([
    (0, decorators_1.OnEvent)({ name: 'AppBootstrap' }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], PluginService.prototype, "onBootstrap", null);
__decorate([
    (0, decorators_1.OnEvent)({ name: 'AssetCreate' }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], PluginService.prototype, "handleAssetCreate", null);
__decorate([
    (0, decorators_1.OnJob)({ name: enum_1.JobName.WorkflowRun, queue: enum_1.QueueName.Workflow }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], PluginService.prototype, "handleWorkflowRun", null);
exports.PluginService = PluginService = __decorate([
    (0, common_1.Injectable)()
], PluginService);
//# sourceMappingURL=plugin.service.js.map