"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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommunityNodeTypesService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const di_1 = require("@n8n/di");
const n8n_workflow_1 = require("n8n-workflow");
const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
const community_node_types_utils_1 = require("./community-node-types-utils");
const community_packages_config_1 = require("./community-packages.config");
const community_packages_service_1 = require("./community-packages.service");
const strapi_utils_1 = require("./strapi-utils");
const UPDATE_INTERVAL = 8 * 60 * 60 * 1000;
const RETRY_INTERVAL = 5 * 60 * 1000;
const STRAPI_ARRAY_LIMIT = 100;
let CommunityNodeTypesService = class CommunityNodeTypesService {
    constructor(logger, config, communityPackagesService) {
        this.logger = logger;
        this.config = config;
        this.communityPackagesService = communityPackagesService;
        this.communityNodeTypes = new Map();
        this.lastUpdateTimestamp = 0;
    }
    async detectUpdates(environment) {
        let communityNodesMetadata = [];
        try {
            communityNodesMetadata = await (0, community_node_types_utils_1.getCommunityNodesMetadata)(environment, this.config.aiNodeSdkVersion);
        }
        catch (error) {
            this.logger.error('Failed to fetch community nodes metadata', {
                error: (0, n8n_workflow_1.ensureError)(error),
            });
            return { scheduleRetry: true };
        }
        const typesToUpdate = [];
        const metadataNames = new Set(communityNodesMetadata.map((entry) => entry.name));
        for (const entry of communityNodesMetadata) {
            const nodeType = this.communityNodeTypes.get(entry.name);
            if (!nodeType ||
                nodeType.npmVersion !== entry.npmVersion ||
                nodeType.updatedAt !== entry.updatedAt) {
                this.logger.debug(`Detected update for community node type: name - ${entry.name}; npmVersion - ${entry.npmVersion}; updatedAt - ${entry.updatedAt};`);
                typesToUpdate.push(entry.id);
            }
        }
        for (const [name, nodeType] of this.communityNodeTypes.entries()) {
            if (!metadataNames.has(name)) {
                this.logger.debug(`Detected removal of community node type: name - ${name}; id - ${nodeType.id};`);
                this.communityNodeTypes.delete(name);
            }
        }
        return { typesToUpdate };
    }
    async fetchNodeTypes() {
        try {
            const environment = this.detectEnvironment();
            let data = [];
            if (this.config.enabled && this.config.verifiedEnabled) {
                if (this.communityNodeTypes.size === 0) {
                    data = await (0, community_node_types_utils_1.getCommunityNodeTypes)(environment, {}, this.config.aiNodeSdkVersion);
                    this.updateCommunityNodeTypes(data);
                    return;
                }
                const { typesToUpdate, scheduleRetry } = await this.detectUpdates(environment);
                if (scheduleRetry) {
                    this.setTimestampForRetry();
                    return;
                }
                if (!typesToUpdate?.length) {
                    this.lastUpdateTimestamp = Date.now();
                    return;
                }
                for (let i = 0; i < typesToUpdate.length; i += STRAPI_ARRAY_LIMIT) {
                    const batch = typesToUpdate.slice(i, i + STRAPI_ARRAY_LIMIT);
                    const qs = (0, strapi_utils_1.buildStrapiUpdateQuery)(batch);
                    const batchData = await (0, community_node_types_utils_1.getCommunityNodeTypes)(environment, qs, this.config.aiNodeSdkVersion);
                    data.push(...batchData);
                }
            }
            this.updateCommunityNodeTypes(data);
        }
        catch (error) {
            this.logger.error('Failed to fetch community node types', { error: (0, n8n_workflow_1.ensureError)(error) });
        }
    }
    detectEnvironment() {
        const environment = process.env.ENVIRONMENT;
        if (environment === 'staging')
            return 'staging';
        if (backend_common_1.inProduction)
            return 'production';
        if (environment === 'production')
            return 'production';
        return 'staging';
    }
    updateCommunityNodeTypes(nodeTypes) {
        if (!nodeTypes?.length) {
            this.setTimestampForRetry();
            return;
        }
        this.setCommunityNodeTypes(nodeTypes);
        this.createAiTools();
        this.lastUpdateTimestamp = Date.now();
    }
    createAiTools() {
        const usableAsTools = Array.from(this.communityNodeTypes.values()).filter((nodeType) => nodeType.nodeDescription.usableAsTool && !(0, n8n_workflow_1.isToolType)(nodeType.name));
        const forbiddenCategories = ['Recommended Tools'];
        for (const nodeType of usableAsTools) {
            const clonedNodeType = (0, cloneDeep_1.default)(nodeType);
            const toolSubcategories = clonedNodeType.nodeDescription.codex?.subcategories?.Tools ?? [
                'Other Tools',
            ];
            const filteredToolSubcategories = toolSubcategories.filter((subcategory) => !forbiddenCategories.includes(subcategory));
            clonedNodeType.name += 'Tool';
            clonedNodeType.nodeDescription.name += 'Tool';
            clonedNodeType.nodeDescription.inputs = [];
            clonedNodeType.nodeDescription.outputs = [n8n_workflow_1.NodeConnectionTypes.AiTool];
            clonedNodeType.nodeDescription.displayName += ' Tool';
            clonedNodeType.nodeDescription.codex = {
                categories: ['AI'],
                subcategories: {
                    AI: ['Tools'],
                    Tools: filteredToolSubcategories,
                },
                resources: clonedNodeType.nodeDescription.codex?.resources ?? {},
            };
            this.communityNodeTypes.set(clonedNodeType.name, clonedNodeType);
        }
    }
    setCommunityNodeTypes(nodeTypes) {
        for (const nodeType of nodeTypes) {
            this.communityNodeTypes.set(nodeType.name, nodeType);
        }
    }
    updateRequired() {
        if (!this.lastUpdateTimestamp)
            return true;
        return Date.now() - this.lastUpdateTimestamp > UPDATE_INTERVAL;
    }
    setTimestampForRetry() {
        const jitter = Math.floor(Math.random() * 4 * 60 * 1000) - 2 * 60 * 1000;
        this.lastUpdateTimestamp = Date.now() - (UPDATE_INTERVAL - RETRY_INTERVAL + jitter);
    }
    async createIsInstalled() {
        const installedPackages = (await this.communityPackagesService.getAllInstalledPackages()) ?? [];
        const installedPackageNames = new Set(installedPackages.map((p) => p.packageName));
        return (nodeTypeName) => installedPackageNames.has(nodeTypeName.split('.')[0]);
    }
    async getCommunityNodeTypes() {
        if (this.updateRequired()) {
            await this.fetchNodeTypes();
        }
        const isInstalled = await this.createIsInstalled();
        return Array.from(this.communityNodeTypes.values()).map((nodeType) => ({
            ...nodeType,
            isInstalled: isInstalled(nodeType.name),
        }));
    }
    async getCommunityNodeType(type) {
        const nodeType = this.communityNodeTypes.get(type);
        const isInstalled = await this.createIsInstalled();
        if (!nodeType)
            return null;
        return { ...nodeType, isInstalled: isInstalled(nodeType.name) };
    }
    async findVetted(packageName) {
        if (this.updateRequired()) {
            await this.fetchNodeTypes();
        }
        const vettedTypes = Array.from(this.communityNodeTypes.values());
        return vettedTypes.find((nodeType) => nodeType.packageName === packageName);
    }
};
exports.CommunityNodeTypesService = CommunityNodeTypesService;
exports.CommunityNodeTypesService = CommunityNodeTypesService = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [backend_common_1.Logger,
        community_packages_config_1.CommunityPackagesConfig,
        community_packages_service_1.CommunityPackagesService])
], CommunityNodeTypesService);
//# sourceMappingURL=community-node-types.service.js.map