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);
};
import ObjectID from "../../Types/ObjectID";
import AlertGroupingRule from "../../Models/DatabaseModels/AlertGroupingRule";
import Alert from "../../Models/DatabaseModels/Alert";
import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
import AlertEpisodeMember, { AlertEpisodeMemberAddedBy, } from "../../Models/DatabaseModels/AlertEpisodeMember";
import AlertEpisodeOwnerUser from "../../Models/DatabaseModels/AlertEpisodeOwnerUser";
import AlertEpisodeOwnerTeam from "../../Models/DatabaseModels/AlertEpisodeOwnerTeam";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import logger from "../Utils/Logger";
import SortOrder from "../../Types/BaseDatabase/SortOrder";
import OneUptimeDate from "../../Types/Date";
import QueryHelper from "../Types/Database/QueryHelper";
import AlertGroupingRuleService from "./AlertGroupingRuleService";
import AlertService from "./AlertService";
import AlertEpisodeService from "./AlertEpisodeService";
import AlertEpisodeMemberService from "./AlertEpisodeMemberService";
import AlertEpisodeOwnerUserService from "./AlertEpisodeOwnerUserService";
import AlertEpisodeOwnerTeamService from "./AlertEpisodeOwnerTeamService";
import MonitorService from "./MonitorService";
import Semaphore from "../Infrastructure/Semaphore";
import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
import { Green500 } from "../../Types/BrandColors";
class AlertGroupingEngineServiceClass {
    async processAlert(alert) {
        var _a, _b, _c, _d, _e, _f, _g;
        logger.debug(`Processing alert ${alert.id} for grouping`, {
            projectId: (_a = alert.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
        });
        try {
            if (!alert.id || !alert.projectId) {
                logger.warn("Alert missing id or projectId, skipping grouping", {
                    projectId: (_b = alert.projectId) === null || _b === void 0 ? void 0 : _b.toString(),
                });
                return { grouped: false };
            }
            // If alert already has an episode, don't reprocess
            if (alert.alertEpisodeId) {
                return { grouped: true, episodeId: alert.alertEpisodeId };
            }
            // Get enabled rules sorted by priority
            const rules = await AlertGroupingRuleService.findBy({
                query: {
                    projectId: alert.projectId,
                    isEnabled: true,
                },
                sort: {
                    priority: SortOrder.Ascending,
                },
                props: {
                    isRoot: true,
                },
                select: {
                    _id: true,
                    name: true,
                    priority: true,
                    // Match criteria fields
                    monitors: {
                        _id: true,
                    },
                    alertSeverities: {
                        _id: true,
                    },
                    alertLabels: {
                        _id: true,
                    },
                    monitorLabels: {
                        _id: true,
                    },
                    alertTitlePattern: true,
                    alertDescriptionPattern: true,
                    monitorNamePattern: true,
                    monitorDescriptionPattern: true,
                    // Group by fields
                    groupByMonitor: true,
                    groupBySeverity: true,
                    groupByAlertTitle: true,
                    groupByAlertLabels: true,
                    groupByMonitorLabels: true,
                    // Time settings
                    enableTimeWindow: true,
                    timeWindowMinutes: true,
                    episodeTitleTemplate: true,
                    episodeDescriptionTemplate: true,
                    enableResolveDelay: true,
                    resolveDelayMinutes: true,
                    enableReopenWindow: true,
                    reopenWindowMinutes: true,
                    enableInactivityTimeout: true,
                    inactivityTimeoutMinutes: true,
                    defaultAssignToUserId: true,
                    defaultAssignToTeamId: true,
                    onCallDutyPolicies: {
                        _id: true,
                    },
                    // Episode configuration fields
                    episodeLabels: {
                        _id: true,
                    },
                    episodeOwnerUsers: {
                        _id: true,
                    },
                    episodeOwnerTeams: {
                        _id: true,
                    },
                },
                limit: 100,
                skip: 0,
            });
            if (rules.length === 0) {
                logger.debug(`No enabled grouping rules found for project ${alert.projectId}`, { projectId: (_c = alert.projectId) === null || _c === void 0 ? void 0 : _c.toString() });
                return { grouped: false };
            }
            logger.debug(`Found ${rules.length} enabled grouping rules for project ${alert.projectId}`, { projectId: (_d = alert.projectId) === null || _d === void 0 ? void 0 : _d.toString() });
            // Find first matching rule
            for (const rule of rules) {
                const matches = await this.doesAlertMatchRule(alert, rule);
                if (matches) {
                    logger.debug(`Alert ${alert.id} matches rule ${rule.name || rule.id}`, { projectId: (_e = alert.projectId) === null || _e === void 0 ? void 0 : _e.toString() });
                    // Try to find existing episode or create new one
                    const result = await this.groupAlertWithRule(alert, rule);
                    return result;
                }
            }
            logger.debug(`Alert ${alert.id} did not match any grouping rules`, {
                projectId: (_f = alert.projectId) === null || _f === void 0 ? void 0 : _f.toString(),
            });
            return { grouped: false };
        }
        catch (error) {
            logger.error(`Error processing alert for grouping: ${error}`, {
                projectId: (_g = alert.projectId) === null || _g === void 0 ? void 0 : _g.toString(),
            });
            return { grouped: false };
        }
    }
    async doesAlertMatchRule(alert, rule) {
        var _a, _b, _c, _d, _e, _f;
        logger.debug(`Checking if alert ${alert.id} matches rule ${rule.name || rule.id}`, { projectId: (_a = alert.projectId) === null || _a === void 0 ? void 0 : _a.toString() });
        // Check monitor IDs - if monitors are specified, alert must be from one of them
        if (rule.monitors && rule.monitors.length > 0) {
            if (!alert.monitorId) {
                return false;
            }
            const monitorIds = rule.monitors.map((m) => {
                var _a;
                return ((_a = m.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const alertMonitorIdStr = alert.monitorId.toString();
            if (!monitorIds.includes(alertMonitorIdStr)) {
                return false;
            }
        }
        // Check alert severity IDs - if severities are specified, alert must have one of them
        if (rule.alertSeverities && rule.alertSeverities.length > 0) {
            if (!alert.alertSeverityId) {
                return false;
            }
            const severityIds = rule.alertSeverities.map((s) => {
                var _a;
                return ((_a = s.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const alertSeverityIdStr = alert.alertSeverityId.toString();
            if (!severityIds.includes(alertSeverityIdStr)) {
                return false;
            }
        }
        // Check alert label IDs - if alert labels are specified, alert must have at least one of them
        if (rule.alertLabels && rule.alertLabels.length > 0) {
            if (!alert.labels || alert.labels.length === 0) {
                return false;
            }
            const ruleLabelIds = rule.alertLabels.map((l) => {
                var _a;
                return ((_a = l.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const alertLabelIds = alert.labels.map((l) => {
                var _a;
                return ((_a = l.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const hasMatchingLabel = ruleLabelIds.some((labelId) => {
                return alertLabelIds.includes(labelId);
            });
            if (!hasMatchingLabel) {
                return false;
            }
        }
        // Check monitor-related criteria (labels, name pattern, description pattern)
        const hasMonitorCriteria = Boolean((rule.monitorLabels && rule.monitorLabels.length > 0) ||
            rule.monitorNamePattern ||
            rule.monitorDescriptionPattern);
        if (hasMonitorCriteria) {
            if (!alert.monitorId) {
                return false;
            }
            // Load monitor with all needed fields
            const monitor = await MonitorService.findOneById({
                id: alert.monitorId,
                select: {
                    name: true,
                    description: true,
                    labels: {
                        _id: true,
                    },
                },
                props: {
                    isRoot: true,
                },
            });
            if (!monitor) {
                return false;
            }
            // Check monitor labels
            if (rule.monitorLabels && rule.monitorLabels.length > 0) {
                if (!monitor.labels || monitor.labels.length === 0) {
                    return false;
                }
                const ruleMonitorLabelIds = rule.monitorLabels.map((l) => {
                    var _a;
                    return ((_a = l.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
                });
                const monitorLabelIds = monitor.labels.map((l) => {
                    var _a;
                    return ((_a = l.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
                });
                const hasMatchingMonitorLabel = ruleMonitorLabelIds.some((labelId) => {
                    return monitorLabelIds.includes(labelId);
                });
                if (!hasMatchingMonitorLabel) {
                    return false;
                }
            }
            // Check monitor name pattern (regex)
            if (rule.monitorNamePattern) {
                if (!monitor.name) {
                    return false;
                }
                try {
                    const regex = new RegExp(rule.monitorNamePattern, "i");
                    if (!regex.test(monitor.name)) {
                        return false;
                    }
                }
                catch (_g) {
                    logger.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.monitorNamePattern}`, { projectId: (_b = alert.projectId) === null || _b === void 0 ? void 0 : _b.toString() });
                    return false;
                }
            }
            // Check monitor description pattern (regex)
            if (rule.monitorDescriptionPattern) {
                if (!monitor.description) {
                    return false;
                }
                try {
                    const regex = new RegExp(rule.monitorDescriptionPattern, "i");
                    if (!regex.test(monitor.description)) {
                        return false;
                    }
                }
                catch (_h) {
                    logger.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.monitorDescriptionPattern}`, { projectId: (_c = alert.projectId) === null || _c === void 0 ? void 0 : _c.toString() });
                    return false;
                }
            }
        }
        // Check alert title pattern (regex)
        if (rule.alertTitlePattern) {
            if (!alert.title) {
                return false;
            }
            try {
                const regex = new RegExp(rule.alertTitlePattern, "i");
                if (!regex.test(alert.title)) {
                    return false;
                }
            }
            catch (_j) {
                logger.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.alertTitlePattern}`, { projectId: (_d = alert.projectId) === null || _d === void 0 ? void 0 : _d.toString() });
                return false;
            }
        }
        // Check alert description pattern (regex)
        if (rule.alertDescriptionPattern) {
            if (!alert.description) {
                return false;
            }
            try {
                const regex = new RegExp(rule.alertDescriptionPattern, "i");
                if (!regex.test(alert.description)) {
                    return false;
                }
            }
            catch (_k) {
                logger.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.alertDescriptionPattern}`, { projectId: (_e = alert.projectId) === null || _e === void 0 ? void 0 : _e.toString() });
                return false;
            }
        }
        // If no criteria specified (all fields empty), rule matches all alerts
        logger.debug(`Rule ${rule.name || rule.id} matched alert ${alert.id} (all criteria passed)`, { projectId: (_f = alert.projectId) === null || _f === void 0 ? void 0 : _f.toString() });
        return true;
    }
    async groupAlertWithRule(alert, rule) {
        var _a, _b, _c, _d, _e, _f, _g;
        // Build the grouping key based on groupBy fields
        const groupingKey = await this.buildGroupingKey(alert, rule);
        // Create mutex key to prevent race conditions when creating episodes
        const mutexKey = `${(_a = alert.projectId) === null || _a === void 0 ? void 0 : _a.toString()}-${(_b = rule.id) === null || _b === void 0 ? void 0 : _b.toString()}-${groupingKey}`;
        let mutex = null;
        try {
            /*
             * Acquire mutex to prevent concurrent episode creation for the same grouping key
             * This is critical - we must have the lock before proceeding to prevent race conditions
             */
            logger.debug(`Acquiring mutex for grouping key: ${mutexKey} for alert ${alert.id}`, { projectId: (_c = alert.projectId) === null || _c === void 0 ? void 0 : _c.toString() });
            mutex = await Semaphore.lock({
                key: mutexKey,
                namespace: "AlertGroupingEngine.groupAlertWithRule",
                lockTimeout: 30000, // 30 seconds - enough time to complete episode creation
                acquireTimeout: 60000, // Wait up to 60 seconds to acquire the lock
            });
            logger.debug(`Acquired mutex for grouping key: ${mutexKey} for alert ${alert.id}`, { projectId: (_d = alert.projectId) === null || _d === void 0 ? void 0 : _d.toString() });
            // Calculate time window cutoff (only if time window is enabled)
            let timeWindowCutoff = null;
            if (rule.enableTimeWindow) {
                const timeWindowMinutes = rule.timeWindowMinutes || 60;
                timeWindowCutoff = OneUptimeDate.getSomeMinutesAgo(timeWindowMinutes);
            }
            // Find existing active episode that matches
            const existingEpisode = await this.findMatchingActiveEpisode(alert.projectId, rule.id, groupingKey, timeWindowCutoff);
            if (existingEpisode && existingEpisode.id) {
                // Add alert to existing episode
                await this.addAlertToEpisode(alert, existingEpisode.id, AlertEpisodeMemberAddedBy.Rule, rule.id);
                // Update episode severity if alert has higher severity
                if (alert.alertSeverityId) {
                    await AlertEpisodeService.updateEpisodeSeverity({
                        episodeId: existingEpisode.id,
                        severityId: alert.alertSeverityId,
                        onlyIfHigher: true,
                    });
                }
                return {
                    grouped: true,
                    episodeId: existingEpisode.id,
                    isNewEpisode: false,
                };
            }
            // Check if we can reopen a recently resolved episode (only if enabled)
            if (rule.enableReopenWindow) {
                const reopenWindowMinutes = rule.reopenWindowMinutes || 0;
                if (reopenWindowMinutes > 0) {
                    const reopenCutoff = OneUptimeDate.getSomeMinutesAgo(reopenWindowMinutes);
                    const recentlyResolvedEpisode = await this.findRecentlyResolvedEpisode(alert.projectId, rule.id, groupingKey, reopenCutoff);
                    if (recentlyResolvedEpisode && recentlyResolvedEpisode.id) {
                        // Reopen the episode
                        await AlertEpisodeService.reopenEpisode(recentlyResolvedEpisode.id);
                        // Add alert to reopened episode
                        await this.addAlertToEpisode(alert, recentlyResolvedEpisode.id, AlertEpisodeMemberAddedBy.Rule, rule.id);
                        // Update episode severity if alert has higher severity
                        if (alert.alertSeverityId) {
                            await AlertEpisodeService.updateEpisodeSeverity({
                                episodeId: recentlyResolvedEpisode.id,
                                severityId: alert.alertSeverityId,
                                onlyIfHigher: true,
                            });
                        }
                        return {
                            grouped: true,
                            episodeId: recentlyResolvedEpisode.id,
                            isNewEpisode: false,
                            wasReopened: true,
                        };
                    }
                }
            }
            // Create new episode
            const newEpisode = await this.createNewEpisode(alert, rule, groupingKey);
            if (newEpisode && newEpisode.id) {
                // Add alert to new episode
                await this.addAlertToEpisode(alert, newEpisode.id, AlertEpisodeMemberAddedBy.Rule, rule.id);
                return { grouped: true, episodeId: newEpisode.id, isNewEpisode: true };
            }
            return { grouped: false };
        }
        finally {
            // Release mutex
            if (mutex) {
                try {
                    logger.debug(`Releasing mutex for grouping key: ${mutexKey} for alert ${alert.id}`, { projectId: (_e = alert.projectId) === null || _e === void 0 ? void 0 : _e.toString() });
                    await Semaphore.release(mutex);
                    logger.debug(`Released mutex for grouping key: ${mutexKey} for alert ${alert.id}`, { projectId: (_f = alert.projectId) === null || _f === void 0 ? void 0 : _f.toString() });
                }
                catch (err) {
                    logger.error(`Error releasing mutex for grouping key: ${mutexKey}: ${err}`, { projectId: (_g = alert.projectId) === null || _g === void 0 ? void 0 : _g.toString() });
                }
            }
        }
    }
    async buildGroupingKey(alert, rule) {
        const parts = [];
        // Group by monitor - only if explicitly enabled
        if (rule.groupByMonitor && alert.monitorId) {
            parts.push(`monitor:${alert.monitorId.toString()}`);
        }
        // Group by severity - only if explicitly enabled
        if (rule.groupBySeverity && alert.alertSeverityId) {
            parts.push(`severity:${alert.alertSeverityId.toString()}`);
        }
        // Group by alert title - only if explicitly enabled
        if (rule.groupByAlertTitle && alert.title) {
            // Normalize title for grouping (remove numbers, etc.)
            const normalizedTitle = alert.title
                .toLowerCase()
                .replace(/\d+/g, "X");
            parts.push(`title:${normalizedTitle}`);
        }
        // Group by alert labels (exact set match) - only if explicitly enabled
        if (rule.groupByAlertLabels && alert.id) {
            const alertLabels = await this.getAlertLabels(alert);
            const sortedLabelIds = alertLabels
                .map((l) => {
                var _a;
                return ((_a = l.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            })
                .filter((id) => {
                return id.length > 0;
            })
                .sort();
            parts.push(`alertLabels:${sortedLabelIds.join(",")}`);
        }
        // Group by monitor labels (exact set match) - only if explicitly enabled
        if (rule.groupByMonitorLabels && alert.monitorId) {
            const monitorLabels = await this.getMonitorLabels(alert.monitorId);
            const sortedLabelIds = monitorLabels
                .map((l) => {
                var _a;
                return ((_a = l.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            })
                .filter((id) => {
                return id.length > 0;
            })
                .sort();
            parts.push(`monitorLabels:${sortedLabelIds.join(",")}`);
        }
        // If no group by options are enabled, all matching alerts go into a single episode
        return parts.join("|") || "default";
    }
    async getAlertLabels(alert) {
        // If labels are already loaded on the alert, use them
        if (alert.labels && Array.isArray(alert.labels)) {
            return alert.labels;
        }
        if (!alert.id) {
            return [];
        }
        // Re-load alert with labels
        const reloadedAlert = await AlertService.findOneById({
            id: alert.id,
            select: {
                labels: {
                    _id: true,
                },
            },
            props: {
                isRoot: true,
            },
        });
        return (reloadedAlert === null || reloadedAlert === void 0 ? void 0 : reloadedAlert.labels) || [];
    }
    async getMonitorLabels(monitorId) {
        const monitor = await MonitorService.findOneById({
            id: monitorId,
            select: {
                labels: {
                    _id: true,
                },
            },
            props: {
                isRoot: true,
            },
        });
        return (monitor === null || monitor === void 0 ? void 0 : monitor.labels) || [];
    }
    async findMatchingActiveEpisode(projectId, ruleId, groupingKey, timeWindowCutoff) {
        const query = {
            projectId: projectId,
            alertGroupingRuleId: ruleId,
            groupingKey: groupingKey,
            resolvedAt: null, // Only find active (non-resolved) episodes
        };
        // Only add time window filter if enabled
        if (timeWindowCutoff) {
            query.lastAlertAddedAt = QueryHelper.greaterThanEqualTo(timeWindowCutoff);
        }
        const episode = await AlertEpisodeService.findOneBy({
            query: query,
            sort: {
                lastAlertAddedAt: SortOrder.Descending,
            },
            select: {
                _id: true,
                lastAlertAddedAt: true,
            },
            props: {
                isRoot: true,
            },
        });
        return episode;
    }
    async findRecentlyResolvedEpisode(projectId, ruleId, groupingKey, reopenCutoff) {
        // Find recently resolved episode with matching rule and grouping key
        const episode = await AlertEpisodeService.findOneBy({
            query: {
                projectId: projectId,
                alertGroupingRuleId: ruleId,
                groupingKey: groupingKey,
                resolvedAt: QueryHelper.greaterThanEqualTo(reopenCutoff),
            },
            sort: {
                resolvedAt: SortOrder.Descending,
            },
            select: {
                _id: true,
                resolvedAt: true,
            },
            props: {
                isRoot: true,
            },
        });
        return episode;
    }
    async createNewEpisode(alert, rule, groupingKey) {
        var _a, _b, _c, _d;
        // Generate episode title from template (with initial alertCount of 1)
        const title = this.generateEpisodeTitle(alert, rule.episodeTitleTemplate, 1);
        // Generate episode description from template (with initial alertCount of 1)
        const description = this.generateEpisodeDescription(alert, rule.episodeDescriptionTemplate, 1);
        const newEpisode = new AlertEpisode();
        newEpisode.projectId = alert.projectId;
        newEpisode.title = title;
        if (description) {
            newEpisode.description = description;
        }
        /*
         * Store preprocessed templates for dynamic variable updates
         * Static variables are replaced, dynamic ones (like {{alertCount}}) remain as placeholders
         */
        if (rule.episodeTitleTemplate) {
            newEpisode.titleTemplate = this.preprocessTemplate(alert, rule.episodeTitleTemplate);
        }
        if (rule.episodeDescriptionTemplate) {
            newEpisode.descriptionTemplate = this.preprocessTemplate(alert, rule.episodeDescriptionTemplate);
        }
        newEpisode.alertGroupingRuleId = rule.id;
        newEpisode.groupingKey = groupingKey;
        newEpisode.isManuallyCreated = false;
        newEpisode.lastAlertAddedAt = OneUptimeDate.getCurrentDate();
        // Set severity from alert
        if (alert.alertSeverityId) {
            newEpisode.alertSeverityId = alert.alertSeverityId;
        }
        // Set default ownership from rule
        if (rule.defaultAssignToUserId) {
            newEpisode.assignedToUserId = rule.defaultAssignToUserId;
        }
        if (rule.defaultAssignToTeamId) {
            newEpisode.assignedToTeamId = rule.defaultAssignToTeamId;
        }
        // Copy on-call policies from rule
        if (rule.onCallDutyPolicies && rule.onCallDutyPolicies.length > 0) {
            newEpisode.onCallDutyPolicies = rule.onCallDutyPolicies;
        }
        // Copy episode labels from rule
        if (rule.episodeLabels && rule.episodeLabels.length > 0) {
            newEpisode.labels = rule.episodeLabels;
        }
        try {
            const createdEpisode = await AlertEpisodeService.create({
                data: newEpisode,
                props: {
                    isRoot: true,
                },
            });
            // Add episode owner users from rule
            if (rule.episodeOwnerUsers &&
                rule.episodeOwnerUsers.length > 0 &&
                createdEpisode.id) {
                for (const user of rule.episodeOwnerUsers) {
                    if (!user.id) {
                        continue;
                    }
                    try {
                        const ownerUser = new AlertEpisodeOwnerUser();
                        ownerUser.projectId = alert.projectId;
                        ownerUser.alertEpisodeId = createdEpisode.id;
                        ownerUser.userId = user.id;
                        await AlertEpisodeOwnerUserService.create({
                            data: ownerUser,
                            props: {
                                isRoot: true,
                            },
                        });
                    }
                    catch (ownerError) {
                        logger.error(`Error adding owner user ${user.id} to episode: ${ownerError}`, { projectId: (_a = alert.projectId) === null || _a === void 0 ? void 0 : _a.toString() });
                    }
                }
            }
            // Add episode owner teams from rule
            if (rule.episodeOwnerTeams &&
                rule.episodeOwnerTeams.length > 0 &&
                createdEpisode.id) {
                for (const team of rule.episodeOwnerTeams) {
                    if (!team.id) {
                        continue;
                    }
                    try {
                        const ownerTeam = new AlertEpisodeOwnerTeam();
                        ownerTeam.projectId = alert.projectId;
                        ownerTeam.alertEpisodeId = createdEpisode.id;
                        ownerTeam.teamId = team.id;
                        await AlertEpisodeOwnerTeamService.create({
                            data: ownerTeam,
                            props: {
                                isRoot: true,
                            },
                        });
                    }
                    catch (ownerError) {
                        logger.error(`Error adding owner team ${team.id} to episode: ${ownerError}`, { projectId: (_b = alert.projectId) === null || _b === void 0 ? void 0 : _b.toString() });
                    }
                }
            }
            // Add episode feed entry for episode creation
            if (createdEpisode.id) {
                const groupByParts = [];
                if (rule.groupByMonitor) {
                    groupByParts.push("Monitor");
                }
                if (rule.groupBySeverity) {
                    groupByParts.push("Severity");
                }
                if (rule.groupByAlertTitle) {
                    groupByParts.push("Alert Title");
                }
                if (rule.groupByAlertLabels) {
                    groupByParts.push("Alert Labels");
                }
                if (rule.groupByMonitorLabels) {
                    groupByParts.push("Monitor Labels");
                }
                const groupByDescription = groupByParts.length > 0
                    ? `Grouping by: ${groupByParts.join(", ")}`
                    : "Grouping all matching alerts together";
                let moreInfo = `**Rule:** ${rule.name || "Unnamed Rule"}\n\n`;
                moreInfo += `**Grouping Key:** \`${groupingKey}\`\n\n`;
                moreInfo += `**${groupByDescription}**`;
                if (rule.enableTimeWindow && rule.timeWindowMinutes) {
                    moreInfo += `\n\n**Time Window:** ${rule.timeWindowMinutes} minutes`;
                }
                try {
                    await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
                        alertEpisodeId: createdEpisode.id,
                        projectId: alert.projectId,
                        alertEpisodeFeedEventType: AlertEpisodeFeedEventType.EpisodeCreated,
                        displayColor: Green500,
                        feedInfoInMarkdown: `🔔 **Episode Created** by grouping rule **${rule.name || "Unnamed Rule"}**`,
                        moreInformationInMarkdown: moreInfo,
                    });
                }
                catch (feedError) {
                    logger.error(`Error creating episode feed for episode creation: ${feedError}`, { projectId: (_c = alert.projectId) === null || _c === void 0 ? void 0 : _c.toString() });
                }
            }
            return createdEpisode;
        }
        catch (error) {
            logger.error(`Error creating new episode: ${error}`, {
                projectId: (_d = alert.projectId) === null || _d === void 0 ? void 0 : _d.toString(),
            });
            return null;
        }
    }
    generateEpisodeTitle(alert, template, alertCount = 1) {
        var _a;
        if (!template) {
            // Default title based on alert
            if ((_a = alert.monitor) === null || _a === void 0 ? void 0 : _a.name) {
                return alert.monitor.name;
            }
            if (alert.title) {
                return alert.title.substring(0, 50);
            }
            return "Untitled Episode";
        }
        return (this.replaceTemplatePlaceholders(alert, template, alertCount) ||
            "Untitled Episode");
    }
    generateEpisodeDescription(alert, template, alertCount = 1) {
        if (!template) {
            return undefined;
        }
        return (this.replaceTemplatePlaceholders(alert, template, alertCount) || undefined);
    }
    replaceTemplatePlaceholders(alert, template, alertCount = 1) {
        var _a, _b;
        let result = template;
        /*
         * Static variables (from first alert)
         * {{alertTitle}}
         */
        if (alert.title) {
            result = result.replace(/\{\{alertTitle\}\}/g, alert.title);
        }
        // {{alertDescription}}
        if (alert.description) {
            result = result.replace(/\{\{alertDescription\}\}/g, alert.description);
        }
        // {{monitorName}}
        if ((_a = alert.monitor) === null || _a === void 0 ? void 0 : _a.name) {
            result = result.replace(/\{\{monitorName\}\}/g, alert.monitor.name);
        }
        // {{alertSeverity}}
        if ((_b = alert.alertSeverity) === null || _b === void 0 ? void 0 : _b.name) {
            result = result.replace(/\{\{alertSeverity\}\}/g, alert.alertSeverity.name);
        }
        /*
         * Dynamic variables (updated when alerts are added/removed)
         * {{alertCount}}
         */
        result = result.replace(/\{\{alertCount\}\}/g, alertCount.toString());
        // Clean up any remaining unknown placeholders
        result = result.replace(/\{\{[^}]+\}\}/g, "");
        return result;
    }
    /*
     * Preprocess template: replace static variables but keep dynamic ones as placeholders
     * This is stored on the episode so we can re-render with updated dynamic values later
     */
    preprocessTemplate(alert, template) {
        var _a, _b;
        let result = template;
        /*
         * Replace static variables (from first alert)
         * {{alertTitle}}
         */
        if (alert.title) {
            result = result.replace(/\{\{alertTitle\}\}/g, alert.title);
        }
        // {{alertDescription}}
        if (alert.description) {
            result = result.replace(/\{\{alertDescription\}\}/g, alert.description);
        }
        // {{monitorName}}
        if ((_a = alert.monitor) === null || _a === void 0 ? void 0 : _a.name) {
            result = result.replace(/\{\{monitorName\}\}/g, alert.monitor.name);
        }
        // {{alertSeverity}}
        if ((_b = alert.alertSeverity) === null || _b === void 0 ? void 0 : _b.name) {
            result = result.replace(/\{\{alertSeverity\}\}/g, alert.alertSeverity.name);
        }
        /*
         * Keep dynamic variables as placeholders (e.g., {{alertCount}})
         * They will be replaced when title/description is re-rendered
         */
        return result;
    }
    async addAlertToEpisode(alert, episodeId, addedBy, ruleId) {
        var _a;
        const member = new AlertEpisodeMember();
        member.projectId = alert.projectId;
        member.alertEpisodeId = episodeId;
        member.alertId = alert.id;
        member.addedBy = addedBy;
        if (ruleId) {
            member.matchedRuleId = ruleId;
        }
        try {
            await AlertEpisodeMemberService.create({
                data: member,
                props: {
                    isRoot: true,
                },
            });
            // Feed entries are created by AlertEpisodeMemberService.onCreateSuccess
        }
        catch (error) {
            // Check if it's a duplicate error (alert already in episode)
            if (error instanceof Error &&
                error.message.includes("already a member")) {
                logger.debug(`Alert ${alert.id} is already in episode ${episodeId}`, {
                    projectId: (_a = alert.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                });
                return;
            }
            throw error;
        }
    }
    async addAlertToEpisodeManually(alert, episodeId, addedByUserId) {
        const member = new AlertEpisodeMember();
        member.projectId = alert.projectId;
        member.alertEpisodeId = episodeId;
        member.alertId = alert.id;
        member.addedBy = AlertEpisodeMemberAddedBy.Manual;
        if (addedByUserId) {
            member.addedByUserId = addedByUserId;
        }
        await AlertEpisodeMemberService.create({
            data: member,
            props: {
                isRoot: true,
            },
        });
        // Feed entries are created by AlertEpisodeMemberService.onCreateSuccess
        // Update episode severity if needed
        if (alert.alertSeverityId) {
            await AlertEpisodeService.updateEpisodeSeverity({
                episodeId: episodeId,
                severityId: alert.alertSeverityId,
                onlyIfHigher: true,
            });
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Alert]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "processAlert", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Alert,
        AlertGroupingRule]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "doesAlertMatchRule", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Alert,
        AlertGroupingRule]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "groupAlertWithRule", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Alert,
        AlertGroupingRule]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "buildGroupingKey", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Alert]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "getAlertLabels", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "getMonitorLabels", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID, String, Object]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "findMatchingActiveEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID, String, Date]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "findRecentlyResolvedEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Alert,
        AlertGroupingRule, String]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "createNewEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Alert,
        ObjectID, String, ObjectID]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "addAlertToEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Alert,
        ObjectID,
        ObjectID]),
    __metadata("design:returntype", Promise)
], AlertGroupingEngineServiceClass.prototype, "addAlertToEpisodeManually", null);
export default new AlertGroupingEngineServiceClass();
//# sourceMappingURL=AlertGroupingEngineService.js.map