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 IncidentGroupingRule from "../../Models/DatabaseModels/IncidentGroupingRule";
import Incident from "../../Models/DatabaseModels/Incident";
import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
import IncidentEpisodeMember, { IncidentEpisodeMemberAddedBy, } from "../../Models/DatabaseModels/IncidentEpisodeMember";
import IncidentEpisodeOwnerUser from "../../Models/DatabaseModels/IncidentEpisodeOwnerUser";
import IncidentEpisodeOwnerTeam from "../../Models/DatabaseModels/IncidentEpisodeOwnerTeam";
import IncidentEpisodeRoleMember from "../../Models/DatabaseModels/IncidentEpisodeRoleMember";
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 IncidentGroupingRuleService from "./IncidentGroupingRuleService";
import IncidentService from "./IncidentService";
import IncidentEpisodeService from "./IncidentEpisodeService";
import IncidentEpisodeMemberService from "./IncidentEpisodeMemberService";
import IncidentEpisodeOwnerUserService from "./IncidentEpisodeOwnerUserService";
import IncidentEpisodeOwnerTeamService from "./IncidentEpisodeOwnerTeamService";
import IncidentEpisodeRoleMemberService from "./IncidentEpisodeRoleMemberService";
import MonitorService from "./MonitorService";
import Semaphore from "../Infrastructure/Semaphore";
import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
import { IncidentEpisodeFeedEventType } from "../../Models/DatabaseModels/IncidentEpisodeFeed";
import { Green500 } from "../../Types/BrandColors";
class IncidentGroupingEngineServiceClass {
    async processIncident(incident) {
        var _a, _b, _c, _d, _e, _f, _g;
        logger.debug(`Processing incident ${incident.id} for grouping`, {
            projectId: (_a = incident.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
        });
        try {
            if (!incident.id || !incident.projectId) {
                logger.warn("Incident missing id or projectId, skipping grouping", {
                    projectId: (_b = incident.projectId) === null || _b === void 0 ? void 0 : _b.toString(),
                });
                return { grouped: false };
            }
            // If incident already has an episode, don't reprocess
            if (incident.incidentEpisodeId) {
                return { grouped: true, episodeId: incident.incidentEpisodeId };
            }
            // Get enabled rules sorted by priority
            const rules = await IncidentGroupingRuleService.findBy({
                query: {
                    projectId: incident.projectId,
                    isEnabled: true,
                },
                sort: {
                    priority: SortOrder.Ascending,
                },
                props: {
                    isRoot: true,
                },
                select: {
                    _id: true,
                    name: true,
                    priority: true,
                    // Match criteria fields
                    monitors: {
                        _id: true,
                    },
                    incidentSeverities: {
                        _id: true,
                    },
                    incidentLabels: {
                        _id: true,
                    },
                    monitorLabels: {
                        _id: true,
                    },
                    incidentTitlePattern: true,
                    incidentDescriptionPattern: true,
                    monitorNamePattern: true,
                    monitorDescriptionPattern: true,
                    // Group by fields
                    groupByMonitor: true,
                    groupBySeverity: true,
                    groupByIncidentTitle: true,
                    groupByIncidentLabels: 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,
                    },
                    episodeMemberRoles: {
                        _id: true,
                    },
                    episodeMemberRoleAssignments: true,
                },
                limit: 100,
                skip: 0,
            });
            if (rules.length === 0) {
                logger.debug(`No enabled grouping rules found for project ${incident.projectId}`, { projectId: (_c = incident.projectId) === null || _c === void 0 ? void 0 : _c.toString() });
                return { grouped: false };
            }
            logger.debug(`Found ${rules.length} enabled grouping rules for project ${incident.projectId}`, { projectId: (_d = incident.projectId) === null || _d === void 0 ? void 0 : _d.toString() });
            // Find first matching rule
            for (const rule of rules) {
                const matches = await this.doesIncidentMatchRule(incident, rule);
                if (matches) {
                    logger.debug(`Incident ${incident.id} matches rule ${rule.name || rule.id}`, { projectId: (_e = incident.projectId) === null || _e === void 0 ? void 0 : _e.toString() });
                    // Try to find existing episode or create new one
                    const result = await this.groupIncidentWithRule(incident, rule);
                    return result;
                }
            }
            logger.debug(`Incident ${incident.id} did not match any grouping rules`, {
                projectId: (_f = incident.projectId) === null || _f === void 0 ? void 0 : _f.toString(),
            });
            return { grouped: false };
        }
        catch (error) {
            logger.error(`Error processing incident for grouping: ${error}`, {
                projectId: (_g = incident.projectId) === null || _g === void 0 ? void 0 : _g.toString(),
            });
            return { grouped: false };
        }
    }
    async doesIncidentMatchRule(incident, rule) {
        var _a, _b, _c, _d, _e, _f;
        logger.debug(`Checking if incident ${incident.id} matches rule ${rule.name || rule.id}`, { projectId: (_a = incident.projectId) === null || _a === void 0 ? void 0 : _a.toString() });
        // Check monitor IDs - if monitors are specified, incident must be from one of them
        if (rule.monitors && rule.monitors.length > 0) {
            if (!incident.monitors || incident.monitors.length === 0) {
                return false;
            }
            const ruleMonitorIds = rule.monitors.map((m) => {
                var _a;
                return ((_a = m.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const incidentMonitorIds = incident.monitors.map((m) => {
                var _a;
                return ((_a = m.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const hasMatchingMonitor = ruleMonitorIds.some((monitorId) => {
                return incidentMonitorIds.includes(monitorId);
            });
            if (!hasMatchingMonitor) {
                return false;
            }
        }
        // Check incident severity IDs - if severities are specified, incident must have one of them
        if (rule.incidentSeverities && rule.incidentSeverities.length > 0) {
            if (!incident.incidentSeverityId) {
                return false;
            }
            const severityIds = rule.incidentSeverities.map((s) => {
                var _a;
                return ((_a = s.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const incidentSeverityIdStr = incident.incidentSeverityId.toString();
            if (!severityIds.includes(incidentSeverityIdStr)) {
                return false;
            }
        }
        // Check incident label IDs - if incident labels are specified, incident must have at least one of them
        if (rule.incidentLabels && rule.incidentLabels.length > 0) {
            if (!incident.labels || incident.labels.length === 0) {
                return false;
            }
            const ruleLabelIds = rule.incidentLabels.map((l) => {
                var _a;
                return ((_a = l.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const incidentLabelIds = incident.labels.map((l) => {
                var _a;
                return ((_a = l.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
            });
            const hasMatchingLabel = ruleLabelIds.some((labelId) => {
                return incidentLabelIds.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 (!incident.monitors || incident.monitors.length === 0) {
                return false;
            }
            // Check at least one monitor matches all criteria
            let anyMonitorMatches = false;
            for (const incidentMonitor of incident.monitors) {
                if (!incidentMonitor.id) {
                    continue;
                }
                // Load monitor with all needed fields
                const monitor = await MonitorService.findOneById({
                    id: incidentMonitor.id,
                    select: {
                        name: true,
                        description: true,
                        labels: {
                            _id: true,
                        },
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (!monitor) {
                    continue;
                }
                let monitorMatches = true;
                // Check monitor labels
                if (rule.monitorLabels && rule.monitorLabels.length > 0) {
                    if (!monitor.labels || monitor.labels.length === 0) {
                        monitorMatches = false;
                    }
                    else {
                        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) {
                            monitorMatches = false;
                        }
                    }
                }
                // Check monitor name pattern (regex)
                if (monitorMatches && rule.monitorNamePattern) {
                    if (!monitor.name) {
                        monitorMatches = false;
                    }
                    else {
                        try {
                            const regex = new RegExp(rule.monitorNamePattern, "i");
                            if (!regex.test(monitor.name)) {
                                monitorMatches = false;
                            }
                        }
                        catch (_g) {
                            logger.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.monitorNamePattern}`, { projectId: (_b = incident.projectId) === null || _b === void 0 ? void 0 : _b.toString() });
                            monitorMatches = false;
                        }
                    }
                }
                // Check monitor description pattern (regex)
                if (monitorMatches && rule.monitorDescriptionPattern) {
                    if (!monitor.description) {
                        monitorMatches = false;
                    }
                    else {
                        try {
                            const regex = new RegExp(rule.monitorDescriptionPattern, "i");
                            if (!regex.test(monitor.description)) {
                                monitorMatches = false;
                            }
                        }
                        catch (_h) {
                            logger.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.monitorDescriptionPattern}`, { projectId: (_c = incident.projectId) === null || _c === void 0 ? void 0 : _c.toString() });
                            monitorMatches = false;
                        }
                    }
                }
                if (monitorMatches) {
                    anyMonitorMatches = true;
                    break;
                }
            }
            if (!anyMonitorMatches) {
                return false;
            }
        }
        // Check incident title pattern (regex)
        if (rule.incidentTitlePattern) {
            if (!incident.title) {
                return false;
            }
            try {
                const regex = new RegExp(rule.incidentTitlePattern, "i");
                if (!regex.test(incident.title)) {
                    return false;
                }
            }
            catch (_j) {
                logger.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.incidentTitlePattern}`, { projectId: (_d = incident.projectId) === null || _d === void 0 ? void 0 : _d.toString() });
                return false;
            }
        }
        // Check incident description pattern (regex)
        if (rule.incidentDescriptionPattern) {
            if (!incident.description) {
                return false;
            }
            try {
                const regex = new RegExp(rule.incidentDescriptionPattern, "i");
                if (!regex.test(incident.description)) {
                    return false;
                }
            }
            catch (_k) {
                logger.warn(`Invalid regex pattern in rule ${rule.id}: ${rule.incidentDescriptionPattern}`, { projectId: (_e = incident.projectId) === null || _e === void 0 ? void 0 : _e.toString() });
                return false;
            }
        }
        // If no criteria specified (all fields empty), rule matches all incidents
        logger.debug(`Rule ${rule.name || rule.id} matched incident ${incident.id} (all criteria passed)`, { projectId: (_f = incident.projectId) === null || _f === void 0 ? void 0 : _f.toString() });
        return true;
    }
    async groupIncidentWithRule(incident, rule) {
        var _a, _b, _c, _d, _e, _f, _g;
        // Build the grouping key based on groupBy fields
        const groupingKey = await this.buildGroupingKey(incident, rule);
        // Create mutex key to prevent race conditions when creating episodes
        const mutexKey = `${(_a = incident.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 incident ${incident.id}`, { projectId: (_c = incident.projectId) === null || _c === void 0 ? void 0 : _c.toString() });
            mutex = await Semaphore.lock({
                key: mutexKey,
                namespace: "IncidentGroupingEngine.groupIncidentWithRule",
                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 incident ${incident.id}`, { projectId: (_d = incident.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(incident.projectId, rule.id, groupingKey, timeWindowCutoff);
            if (existingEpisode && existingEpisode.id) {
                // Add incident to existing episode
                await this.addIncidentToEpisode(incident, existingEpisode.id, IncidentEpisodeMemberAddedBy.Rule, rule.id);
                // Update episode severity if incident has higher severity
                if (incident.incidentSeverityId) {
                    await IncidentEpisodeService.updateEpisodeSeverity(existingEpisode.id, incident.incidentSeverityId, 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(incident.projectId, rule.id, groupingKey, reopenCutoff);
                    if (recentlyResolvedEpisode && recentlyResolvedEpisode.id) {
                        // Reopen the episode
                        await IncidentEpisodeService.reopenEpisode(recentlyResolvedEpisode.id);
                        // Add incident to reopened episode
                        await this.addIncidentToEpisode(incident, recentlyResolvedEpisode.id, IncidentEpisodeMemberAddedBy.Rule, rule.id);
                        // Update episode severity if incident has higher severity
                        if (incident.incidentSeverityId) {
                            await IncidentEpisodeService.updateEpisodeSeverity(recentlyResolvedEpisode.id, incident.incidentSeverityId, true);
                        }
                        return {
                            grouped: true,
                            episodeId: recentlyResolvedEpisode.id,
                            isNewEpisode: false,
                            wasReopened: true,
                        };
                    }
                }
            }
            // Create new episode
            const newEpisode = await this.createNewEpisode(incident, rule, groupingKey);
            if (newEpisode && newEpisode.id) {
                // Add incident to new episode
                await this.addIncidentToEpisode(incident, newEpisode.id, IncidentEpisodeMemberAddedBy.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 incident ${incident.id}`, { projectId: (_e = incident.projectId) === null || _e === void 0 ? void 0 : _e.toString() });
                    await Semaphore.release(mutex);
                    logger.debug(`Released mutex for grouping key: ${mutexKey} for incident ${incident.id}`, { projectId: (_f = incident.projectId) === null || _f === void 0 ? void 0 : _f.toString() });
                }
                catch (err) {
                    logger.error(`Error releasing mutex for grouping key: ${mutexKey}: ${err}`, { projectId: (_g = incident.projectId) === null || _g === void 0 ? void 0 : _g.toString() });
                }
            }
        }
    }
    async buildGroupingKey(incident, rule) {
        var _a;
        const parts = [];
        // Group by monitor - only if explicitly enabled
        if (rule.groupByMonitor &&
            incident.monitors &&
            incident.monitors.length > 0) {
            // Use the first monitor for grouping key
            const firstMonitor = incident.monitors[0];
            if (firstMonitor && firstMonitor.id) {
                parts.push(`monitor:${firstMonitor.id.toString()}`);
            }
        }
        // Group by severity - only if explicitly enabled
        if (rule.groupBySeverity && incident.incidentSeverityId) {
            parts.push(`severity:${incident.incidentSeverityId.toString()}`);
        }
        // Group by incident title - only if explicitly enabled
        if (rule.groupByIncidentTitle && incident.title) {
            // Normalize title for grouping (remove numbers, etc.)
            const normalizedTitle = incident.title
                .toLowerCase()
                .replace(/\d+/g, "X");
            parts.push(`title:${normalizedTitle}`);
        }
        // Group by incident labels (exact set match) - only if explicitly enabled
        if (rule.groupByIncidentLabels && incident.id) {
            const incidentLabels = await this.getIncidentLabels(incident);
            const sortedLabelIds = incidentLabels
                .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(`incidentLabels:${sortedLabelIds.join(",")}`);
        }
        /*
         * Group by monitor labels (exact set match) - only if explicitly enabled
         * Incidents can be associated with multiple monitors; we union and dedupe labels across all of them.
         */
        if (rule.groupByMonitorLabels &&
            incident.monitors &&
            incident.monitors.length > 0) {
            const monitorLabelIdSet = new Set();
            for (const incidentMonitor of incident.monitors) {
                if (!incidentMonitor || !incidentMonitor.id) {
                    continue;
                }
                const monitorLabels = await this.getMonitorLabels(incidentMonitor.id);
                for (const label of monitorLabels) {
                    const labelIdStr = ((_a = label.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
                    if (labelIdStr.length > 0) {
                        monitorLabelIdSet.add(labelIdStr);
                    }
                }
            }
            const sortedLabelIds = Array.from(monitorLabelIdSet).sort();
            parts.push(`monitorLabels:${sortedLabelIds.join(",")}`);
        }
        // If no group by options are enabled, all matching incidents go into a single episode
        return parts.join("|") || "default";
    }
    async getIncidentLabels(incident) {
        // If labels are already loaded on the incident, use them
        if (incident.labels && Array.isArray(incident.labels)) {
            return incident.labels;
        }
        if (!incident.id) {
            return [];
        }
        // Re-load incident with labels
        const reloadedIncident = await IncidentService.findOneById({
            id: incident.id,
            select: {
                labels: {
                    _id: true,
                },
            },
            props: {
                isRoot: true,
            },
        });
        return (reloadedIncident === null || reloadedIncident === void 0 ? void 0 : reloadedIncident.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,
            incidentGroupingRuleId: ruleId,
            groupingKey: groupingKey,
            resolvedAt: null, // Only find active (non-resolved) episodes
        };
        // Only add time window filter if enabled
        if (timeWindowCutoff) {
            query.lastIncidentAddedAt =
                QueryHelper.greaterThanEqualTo(timeWindowCutoff);
        }
        const episode = await IncidentEpisodeService.findOneBy({
            query: query,
            sort: {
                lastIncidentAddedAt: SortOrder.Descending,
            },
            select: {
                _id: true,
                lastIncidentAddedAt: 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 IncidentEpisodeService.findOneBy({
            query: {
                projectId: projectId,
                incidentGroupingRuleId: ruleId,
                groupingKey: groupingKey,
                resolvedAt: QueryHelper.greaterThanEqualTo(reopenCutoff),
            },
            sort: {
                resolvedAt: SortOrder.Descending,
            },
            select: {
                _id: true,
                resolvedAt: true,
            },
            props: {
                isRoot: true,
            },
        });
        return episode;
    }
    async createNewEpisode(incident, rule, groupingKey) {
        var _a, _b, _c, _d, _e;
        // Generate episode title from template (with initial incidentCount of 1)
        const title = this.generateEpisodeTitle(incident, rule.episodeTitleTemplate, 1);
        // Generate episode description from template (with initial incidentCount of 1)
        const description = this.generateEpisodeDescription(incident, rule.episodeDescriptionTemplate, 1);
        const newEpisode = new IncidentEpisode();
        newEpisode.projectId = incident.projectId;
        newEpisode.title = title;
        if (description) {
            newEpisode.description = description;
        }
        /*
         * Store preprocessed templates for dynamic variable updates
         * Static variables are replaced, dynamic ones (like {{incidentCount}}) remain as placeholders
         */
        if (rule.episodeTitleTemplate) {
            newEpisode.titleTemplate = this.preprocessTemplate(incident, rule.episodeTitleTemplate);
        }
        if (rule.episodeDescriptionTemplate) {
            newEpisode.descriptionTemplate = this.preprocessTemplate(incident, rule.episodeDescriptionTemplate);
        }
        newEpisode.incidentGroupingRuleId = rule.id;
        newEpisode.groupingKey = groupingKey;
        newEpisode.isManuallyCreated = false;
        newEpisode.lastIncidentAddedAt = OneUptimeDate.getCurrentDate();
        // Set severity from incident
        if (incident.incidentSeverityId) {
            newEpisode.incidentSeverityId = incident.incidentSeverityId;
        }
        // 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 IncidentEpisodeService.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 IncidentEpisodeOwnerUser();
                        ownerUser.projectId = incident.projectId;
                        ownerUser.incidentEpisodeId = createdEpisode.id;
                        ownerUser.userId = user.id;
                        await IncidentEpisodeOwnerUserService.create({
                            data: ownerUser,
                            props: {
                                isRoot: true,
                            },
                        });
                    }
                    catch (ownerError) {
                        logger.error(`Error adding owner user ${user.id} to episode: ${ownerError}`, { projectId: (_a = incident.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 IncidentEpisodeOwnerTeam();
                        ownerTeam.projectId = incident.projectId;
                        ownerTeam.incidentEpisodeId = createdEpisode.id;
                        ownerTeam.teamId = team.id;
                        await IncidentEpisodeOwnerTeamService.create({
                            data: ownerTeam,
                            props: {
                                isRoot: true,
                            },
                        });
                    }
                    catch (ownerError) {
                        logger.error(`Error adding owner team ${team.id} to episode: ${ownerError}`, { projectId: (_b = incident.projectId) === null || _b === void 0 ? void 0 : _b.toString() });
                    }
                }
            }
            // Add episode member role assignments from rule
            if (rule.episodeMemberRoleAssignments &&
                rule.episodeMemberRoleAssignments.length > 0 &&
                createdEpisode.id) {
                for (const assignment of rule.episodeMemberRoleAssignments) {
                    if (!assignment.userId || !assignment.incidentRoleId) {
                        continue;
                    }
                    try {
                        const roleMember = new IncidentEpisodeRoleMember();
                        roleMember.projectId = incident.projectId;
                        roleMember.incidentEpisodeId = createdEpisode.id;
                        roleMember.userId = new ObjectID(assignment.userId);
                        roleMember.incidentRoleId = new ObjectID(assignment.incidentRoleId);
                        await IncidentEpisodeRoleMemberService.create({
                            data: roleMember,
                            props: {
                                isRoot: true,
                            },
                        });
                    }
                    catch (memberError) {
                        logger.error(`Error adding member role assignment to episode: ${memberError}`, { projectId: (_c = incident.projectId) === null || _c === void 0 ? void 0 : _c.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.groupByIncidentTitle) {
                    groupByParts.push("Incident Title");
                }
                if (rule.groupByIncidentLabels) {
                    groupByParts.push("Incident Labels");
                }
                if (rule.groupByMonitorLabels) {
                    groupByParts.push("Monitor Labels");
                }
                const groupByDescription = groupByParts.length > 0
                    ? `Grouping by: ${groupByParts.join(", ")}`
                    : "Grouping all matching incidents 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 IncidentEpisodeFeedService.createIncidentEpisodeFeedItem({
                        incidentEpisodeId: createdEpisode.id,
                        projectId: incident.projectId,
                        incidentEpisodeFeedEventType: IncidentEpisodeFeedEventType.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: (_d = incident.projectId) === null || _d === void 0 ? void 0 : _d.toString() });
                }
            }
            return createdEpisode;
        }
        catch (error) {
            logger.error(`Error creating new episode: ${error}`, {
                projectId: (_e = incident.projectId) === null || _e === void 0 ? void 0 : _e.toString(),
            });
            return null;
        }
    }
    generateEpisodeTitle(incident, template, incidentCount = 1) {
        var _a;
        if (!template) {
            // Default title based on incident
            if (incident.monitors &&
                incident.monitors.length > 0 &&
                ((_a = incident.monitors[0]) === null || _a === void 0 ? void 0 : _a.name)) {
                return incident.monitors[0].name;
            }
            if (incident.title) {
                return incident.title.substring(0, 50);
            }
            return "Untitled Episode";
        }
        return (this.replaceTemplatePlaceholders(incident, template, incidentCount) ||
            "Untitled Episode");
    }
    generateEpisodeDescription(incident, template, incidentCount = 1) {
        if (!template) {
            return undefined;
        }
        return (this.replaceTemplatePlaceholders(incident, template, incidentCount) ||
            undefined);
    }
    replaceTemplatePlaceholders(incident, template, incidentCount = 1) {
        var _a, _b;
        let result = template;
        /*
         * Static variables (from first incident)
         * {{incidentTitle}}
         */
        if (incident.title) {
            result = result.replace(/\{\{incidentTitle\}\}/g, incident.title);
        }
        // {{incidentDescription}}
        if (incident.description) {
            result = result.replace(/\{\{incidentDescription\}\}/g, incident.description);
        }
        // {{monitorName}} - use first monitor's name
        if (incident.monitors &&
            incident.monitors.length > 0 &&
            ((_a = incident.monitors[0]) === null || _a === void 0 ? void 0 : _a.name)) {
            result = result.replace(/\{\{monitorName\}\}/g, incident.monitors[0].name);
        }
        // {{incidentSeverity}}
        if ((_b = incident.incidentSeverity) === null || _b === void 0 ? void 0 : _b.name) {
            result = result.replace(/\{\{incidentSeverity\}\}/g, incident.incidentSeverity.name);
        }
        /*
         * Dynamic variables (updated when incidents are added/removed)
         * {{incidentCount}}
         */
        result = result.replace(/\{\{incidentCount\}\}/g, incidentCount.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(incident, template) {
        var _a, _b;
        let result = template;
        /*
         * Replace static variables (from first incident)
         * {{incidentTitle}}
         */
        if (incident.title) {
            result = result.replace(/\{\{incidentTitle\}\}/g, incident.title);
        }
        // {{incidentDescription}}
        if (incident.description) {
            result = result.replace(/\{\{incidentDescription\}\}/g, incident.description);
        }
        // {{monitorName}} - use first monitor's name
        if (incident.monitors &&
            incident.monitors.length > 0 &&
            ((_a = incident.monitors[0]) === null || _a === void 0 ? void 0 : _a.name)) {
            result = result.replace(/\{\{monitorName\}\}/g, incident.monitors[0].name);
        }
        // {{incidentSeverity}}
        if ((_b = incident.incidentSeverity) === null || _b === void 0 ? void 0 : _b.name) {
            result = result.replace(/\{\{incidentSeverity\}\}/g, incident.incidentSeverity.name);
        }
        /*
         * Keep dynamic variables as placeholders (e.g., {{incidentCount}})
         * They will be replaced when title/description is re-rendered
         */
        return result;
    }
    async addIncidentToEpisode(incident, episodeId, addedBy, ruleId) {
        var _a;
        const member = new IncidentEpisodeMember();
        member.projectId = incident.projectId;
        member.incidentEpisodeId = episodeId;
        member.incidentId = incident.id;
        member.addedBy = addedBy;
        if (ruleId) {
            member.matchedRuleId = ruleId;
        }
        try {
            await IncidentEpisodeMemberService.create({
                data: member,
                props: {
                    isRoot: true,
                },
            });
            // Feed entries are created by IncidentEpisodeMemberService.onCreateSuccess
        }
        catch (error) {
            // Check if it's a duplicate error (incident already in episode)
            if (error instanceof Error &&
                error.message.includes("already a member")) {
                logger.debug(`Incident ${incident.id} is already in episode ${episodeId}`, { projectId: (_a = incident.projectId) === null || _a === void 0 ? void 0 : _a.toString() });
                return;
            }
            throw error;
        }
    }
    async addIncidentToEpisodeManually(incident, episodeId, addedByUserId) {
        const member = new IncidentEpisodeMember();
        member.projectId = incident.projectId;
        member.incidentEpisodeId = episodeId;
        member.incidentId = incident.id;
        member.addedBy = IncidentEpisodeMemberAddedBy.Manual;
        if (addedByUserId) {
            member.addedByUserId = addedByUserId;
        }
        await IncidentEpisodeMemberService.create({
            data: member,
            props: {
                isRoot: true,
            },
        });
        // Feed entries are created by IncidentEpisodeMemberService.onCreateSuccess
        // Update episode severity if needed
        if (incident.incidentSeverityId) {
            await IncidentEpisodeService.updateEpisodeSeverity(episodeId, incident.incidentSeverityId, true);
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Incident]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "processIncident", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Incident,
        IncidentGroupingRule]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "doesIncidentMatchRule", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Incident,
        IncidentGroupingRule]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "groupIncidentWithRule", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Incident,
        IncidentGroupingRule]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "buildGroupingKey", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Incident]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "getIncidentLabels", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "getMonitorLabels", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID, String, Object]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "findMatchingActiveEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID, String, Date]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "findRecentlyResolvedEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Incident,
        IncidentGroupingRule, String]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "createNewEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Incident,
        ObjectID, String, ObjectID]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "addIncidentToEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Incident,
        ObjectID,
        ObjectID]),
    __metadata("design:returntype", Promise)
], IncidentGroupingEngineServiceClass.prototype, "addIncidentToEpisodeManually", null);
export default new IncidentGroupingEngineServiceClass();
//# sourceMappingURL=IncidentGroupingEngineService.js.map