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 { applyAlertEpisodeSelfPrivacyFilter } from "../Utils/AlertEpisode/AlertEpisodePrivacyFilter";
import DatabaseService from "./DatabaseService";
import AlertStateService from "./AlertStateService";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import Model from "../../Models/DatabaseModels/AlertEpisode";
import SortOrder from "../../Types/BaseDatabase/SortOrder";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import logger from "../Utils/Logger";
import AlertEpisodeStateTimeline from "../../Models/DatabaseModels/AlertEpisodeStateTimeline";
import AlertEpisodeStateTimelineService from "./AlertEpisodeStateTimelineService";
import { IsBillingEnabled } from "../EnvironmentConfig";
import OneUptimeDate from "../../Types/Date";
import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
import { Red500, Green500, Yellow500, Purple500, } from "../../Types/BrandColors";
import URL from "../../Types/API/URL";
import DatabaseConfig from "../DatabaseConfig";
import AlertSeverityService from "./AlertSeverityService";
import AlertEpisodeMemberService from "./AlertEpisodeMemberService";
import AlertEpisodeOwnerUserService from "./AlertEpisodeOwnerUserService";
import AlertEpisodeOwnerTeamService from "./AlertEpisodeOwnerTeamService";
import TeamMemberService from "./TeamMemberService";
import AlertEpisodeOwnerUser from "../../Models/DatabaseModels/AlertEpisodeOwnerUser";
import AlertEpisodeOwnerTeam from "../../Models/DatabaseModels/AlertEpisodeOwnerTeam";
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
import AlertEpisodeWorkspaceMessages from "../Utils/Workspace/WorkspaceMessages/AlertEpisode";
import Typeof from "../../Types/Typeof";
import AlertService from "./AlertService";
import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
import AlertEpisodeLabelRuleEngineService from "./AlertEpisodeLabelRuleEngineService";
import AlertEpisodeOnCallRuleEngineService from "./AlertEpisodeOnCallRuleEngineService";
import AlertEpisodeOwnerRuleEngineService from "./AlertEpisodeOwnerRuleEngineService";
import AlertEpisodePrivacyRuleEngineService from "./AlertEpisodePrivacyRuleEngineService";
import UserNotificationEventType from "../../Types/UserNotification/UserNotificationEventType";
import ProjectService from "./ProjectService";
export class Service extends DatabaseService {
    constructor() {
        super(Model);
        if (IsBillingEnabled) {
            this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
        }
    }
    async onBeforeFind(findBy) {
        findBy.query = applyAlertEpisodeSelfPrivacyFilter(findBy.query, findBy.props);
        return { findBy, carryForward: null };
    }
    async countBy(countBy) {
        countBy.query = applyAlertEpisodeSelfPrivacyFilter(countBy.query, countBy.props);
        return super.countBy(countBy);
    }
    async onBeforeUpdate(updateBy) {
        updateBy.query = applyAlertEpisodeSelfPrivacyFilter(updateBy.query, updateBy.props);
        return { updateBy, carryForward: null };
    }
    async onBeforeDelete(deleteBy) {
        deleteBy.query = applyAlertEpisodeSelfPrivacyFilter(deleteBy.query, deleteBy.props);
        return { deleteBy, carryForward: null };
    }
    async onBeforeCreate(createBy) {
        if (!createBy.props.tenantId && !createBy.props.isRoot) {
            throw new BadDataException("ProjectId required to create alert episode.");
        }
        const projectId = createBy.props.tenantId || createBy.data.projectId;
        // Get the created state for episodes
        const alertState = await AlertStateService.findOneBy({
            query: {
                projectId: projectId,
                isCreatedState: true,
            },
            select: {
                _id: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!alertState || !alertState.id) {
            throw new BadDataException("Created alert state not found for this project. Please add created alert state from settings.");
        }
        createBy.data.currentAlertStateId = alertState.id;
        // Auto-generate episode number
        const episodeCounterResult = await ProjectService.incrementAndGetAlertEpisodeCounter(projectId);
        createBy.data.episodeNumber = episodeCounterResult.counter;
        createBy.data.episodeNumberWithPrefix = episodeCounterResult.prefix
            ? `${episodeCounterResult.prefix}${episodeCounterResult.counter}`
            : `#${episodeCounterResult.counter}`;
        // Set initial lastAlertAddedAt
        if (!createBy.data.lastAlertAddedAt) {
            createBy.data.lastAlertAddedAt = OneUptimeDate.getCurrentDate();
        }
        return { createBy, carryForward: null };
    }
    async onCreateSuccess(_onCreate, createdItem) {
        if (!createdItem.projectId) {
            throw new BadDataException("projectId is required");
        }
        if (!createdItem.id) {
            throw new BadDataException("id is required");
        }
        if (!createdItem.currentAlertStateId) {
            throw new BadDataException("currentAlertStateId is required");
        }
        // Create initial state timeline entry
        Promise.resolve()
            .then(async () => {
            var _a, _b;
            /*
             * Apply privacy rules BEFORE workspace operations so the workspace
             * channel is created with the correct privacy setting. This may set
             * createdItem.isPrivate=true in memory.
             */
            try {
                await AlertEpisodePrivacyRuleEngineService.applyRulesToEpisode(createdItem);
            }
            catch (error) {
                logger.error(`Apply alert episode privacy rules failed in AlertEpisodeService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
        })
            .then(async () => {
            var _a, _b;
            try {
                if (createdItem.projectId && createdItem.id) {
                    await this.handleEpisodeWorkspaceOperationsAsync(createdItem);
                }
            }
            catch (error) {
                logger.error(`Workspace operations failed in AlertEpisodeService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
        })
            .then(async () => {
            var _a, _b;
            try {
                await this.changeEpisodeState({
                    projectId: createdItem.projectId,
                    episodeId: createdItem.id,
                    alertStateId: createdItem.currentAlertStateId,
                    notifyOwners: false,
                    rootCause: undefined,
                    props: {
                        isRoot: true,
                    },
                });
            }
            catch (error) {
                logger.error(`Handle episode state change failed in AlertEpisodeService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
        })
            .then(async () => {
            var _a, _b;
            try {
                await this.createEpisodeCreatedFeed(createdItem);
            }
            catch (error) {
                logger.error(`Create episode feed failed in AlertEpisodeService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
        })
            .then(async () => {
            var _a, _b;
            // Apply owner rules: add matched owner users/teams to the episode.
            try {
                await AlertEpisodeOwnerRuleEngineService.applyRulesToEpisode(createdItem);
            }
            catch (error) {
                logger.error(`Apply alert episode owner rules failed in AlertEpisodeService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
        })
            .then(async () => {
            var _a, _b;
            // Apply label rules: attach matched labels to the episode.
            try {
                await AlertEpisodeLabelRuleEngineService.applyRulesToEpisode(createdItem);
            }
            catch (error) {
                logger.error(`Apply alert episode label rules failed in AlertEpisodeService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
        })
            .then(async () => {
            var _a, _b;
            /*
             * Apply on-call rules: merge matched policies into the episode's
             * onCallDutyPolicies before the fan-out below picks them up.
             */
            try {
                await AlertEpisodeOnCallRuleEngineService.applyRulesToEpisode(createdItem);
            }
            catch (error) {
                logger.error(`Apply alert episode on-call rules failed in AlertEpisodeService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
        })
            .then(async () => {
            var _a, _b;
            // Execute on-call duty policies
            try {
                await this.executeEpisodeOnCallDutyPoliciesAsync(createdItem);
            }
            catch (error) {
                logger.error(`On-call duty policy execution failed in AlertEpisodeService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
        })
            .catch((error) => {
            var _a, _b;
            logger.error(`Critical error in AlertEpisodeService.onCreateSuccess: ${error}`, {
                projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                alertEpisodeId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
            });
        });
        return createdItem;
    }
    async handleEpisodeWorkspaceOperationsAsync(createdItem) {
        var _a, _b, _c;
        try {
            if (!createdItem.projectId || !createdItem.id) {
                throw new BadDataException("projectId and id are required for workspace operations");
            }
            const workspaceResult = await AlertEpisodeWorkspaceMessages.createChannelsAndInviteUsersToChannels(Object.assign(Object.assign({ projectId: createdItem.projectId, alertEpisodeId: createdItem.id, episodeNumber: createdItem.episodeNumber || 0 }, (createdItem.episodeNumberWithPrefix
                ? {
                    episodeNumberWithPrefix: createdItem.episodeNumberWithPrefix,
                }
                : {})), { isPrivate: createdItem.isPrivate === true }));
            if (workspaceResult && ((_a = workspaceResult.channelsCreated) === null || _a === void 0 ? void 0 : _a.length) > 0) {
                await this.updateOneById({
                    id: createdItem.id,
                    data: {
                        postUpdatesToWorkspaceChannels: workspaceResult.channelsCreated || [],
                    },
                    props: {
                        isRoot: true,
                    },
                });
            }
        }
        catch (error) {
            logger.error(`Error in handleEpisodeWorkspaceOperationsAsync: ${error}`, {
                projectId: (_b = createdItem.projectId) === null || _b === void 0 ? void 0 : _b.toString(),
                alertEpisodeId: (_c = createdItem.id) === null || _c === void 0 ? void 0 : _c.toString(),
            });
            throw error;
        }
    }
    async createEpisodeCreatedFeed(episode) {
        var _a;
        if (!episode.id || !episode.projectId) {
            return;
        }
        let feedInfoInMarkdown = `#### Episode ${episode.episodeNumberWithPrefix || "#" + ((_a = episode.episodeNumber) === null || _a === void 0 ? void 0 : _a.toString())} Created

**${episode.title || "No title provided."}**

`;
        if (episode.description) {
            feedInfoInMarkdown += `${episode.description}\n\n`;
        }
        if (episode.isManuallyCreated) {
            feedInfoInMarkdown += `This episode was manually created.\n\n`;
        }
        const episodeCreateMessageBlocks = await AlertEpisodeWorkspaceMessages.getAlertEpisodeCreateMessageBlocks({
            alertEpisodeId: episode.id,
            projectId: episode.projectId,
        });
        await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
            alertEpisodeId: episode.id,
            projectId: episode.projectId,
            alertEpisodeFeedEventType: AlertEpisodeFeedEventType.EpisodeCreated,
            displayColor: Red500,
            feedInfoInMarkdown: feedInfoInMarkdown,
            userId: episode.createdByUserId || undefined,
            workspaceNotification: {
                appendMessageBlocks: episodeCreateMessageBlocks,
                sendWorkspaceNotification: true,
            },
        });
    }
    async executeEpisodeOnCallDutyPoliciesAsync(createdItem) {
        var _a, _b, _c;
        if (!createdItem.id || !createdItem.projectId) {
            return;
        }
        try {
            // Fetch the episode with on-call duty policies since they may not be loaded
            const episodeWithPolicies = await this.findOneById({
                id: createdItem.id,
                select: {
                    onCallDutyPolicies: {
                        _id: true,
                        name: true,
                    },
                },
                props: {
                    isRoot: true,
                },
            });
            if (!((_a = episodeWithPolicies === null || episodeWithPolicies === void 0 ? void 0 : episodeWithPolicies.onCallDutyPolicies) === null || _a === void 0 ? void 0 : _a.length) ||
                episodeWithPolicies.onCallDutyPolicies.length === 0) {
                return;
            }
            // Execute all on-call policies in parallel
            const policyPromises = episodeWithPolicies.onCallDutyPolicies.map((policy) => {
                return OnCallDutyPolicyService.executePolicy(new ObjectID(policy._id), {
                    triggeredByAlertEpisodeId: createdItem.id,
                    userNotificationEventType: UserNotificationEventType.AlertEpisodeCreated,
                });
            });
            await Promise.allSettled(policyPromises);
            // Update the flag to indicate on-call policy has been executed
            await this.updateOneById({
                id: createdItem.id,
                data: {
                    isOnCallPolicyExecuted: true,
                },
                props: {
                    isRoot: true,
                },
            });
            // Create feed entry for on-call policy execution
            const policyNames = episodeWithPolicies.onCallDutyPolicies
                .map((policy) => {
                return policy.name || "Unnamed Policy";
            })
                .filter((name) => {
                return Boolean(name);
            });
            let feedInfoInMarkdown = `#### On-Call Policy Executed\n\n`;
            feedInfoInMarkdown += `The following on-call ${policyNames.length === 1 ? "policy has" : "policies have"} been executed for this episode:\n\n`;
            for (const policyName of policyNames) {
                feedInfoInMarkdown += `- ${policyName}\n`;
            }
            await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
                alertEpisodeId: createdItem.id,
                projectId: createdItem.projectId,
                alertEpisodeFeedEventType: AlertEpisodeFeedEventType.OnCallPolicy,
                displayColor: Purple500,
                feedInfoInMarkdown: feedInfoInMarkdown,
            });
        }
        catch (error) {
            logger.error(`Error in executeEpisodeOnCallDutyPoliciesAsync: ${error}`, {
                projectId: (_b = createdItem.projectId) === null || _b === void 0 ? void 0 : _b.toString(),
                alertEpisodeId: (_c = createdItem.id) === null || _c === void 0 ? void 0 : _c.toString(),
            });
            throw error;
        }
    }
    async changeEpisodeState(data) {
        const { projectId, episodeId, alertStateId, notifyOwners, rootCause, props, cascadeToAlerts, } = data;
        // Get last episode state timeline
        const lastEpisodeStateTimeline = await AlertEpisodeStateTimelineService.findOneBy({
            query: {
                alertEpisodeId: episodeId,
                projectId: projectId,
            },
            select: {
                _id: true,
                alertStateId: true,
            },
            sort: {
                createdAt: SortOrder.Descending,
            },
            props: {
                isRoot: true,
            },
        });
        if (lastEpisodeStateTimeline &&
            lastEpisodeStateTimeline.alertStateId &&
            lastEpisodeStateTimeline.alertStateId.toString() ===
                alertStateId.toString()) {
            return;
        }
        const stateTimeline = new AlertEpisodeStateTimeline();
        stateTimeline.alertEpisodeId = episodeId;
        stateTimeline.alertStateId = alertStateId;
        stateTimeline.projectId = projectId;
        stateTimeline.isOwnerNotified = !notifyOwners;
        if (rootCause) {
            stateTimeline.rootCause = rootCause;
        }
        await AlertEpisodeStateTimelineService.create({
            data: stateTimeline,
            props: props || {},
        });
        /*
         * Note: resolvedAt is updated by AlertEpisodeStateTimelineService.onCreateSuccess()
         * to avoid duplicate updates.
         */
        // Cascade state change to all member alerts if requested
        if (cascadeToAlerts) {
            await this.cascadeStateToMemberAlerts({
                projectId,
                episodeId,
                alertStateId,
                props,
            });
        }
    }
    async cascadeStateToMemberAlerts(data) {
        const { projectId, episodeId, alertStateId, props } = data;
        // Get all member alerts for this episode
        const members = await AlertEpisodeMemberService.findBy({
            query: {
                alertEpisodeId: episodeId,
                projectId: projectId,
            },
            select: {
                alertId: true,
            },
            props: {
                isRoot: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
        });
        if (members.length === 0) {
            return;
        }
        // Update state for each member alert
        for (const member of members) {
            if (!member.alertId) {
                continue;
            }
            try {
                await AlertService.changeAlertState({
                    projectId: projectId,
                    alertId: member.alertId,
                    alertStateId: alertStateId,
                    notifyOwners: false, // Don't send notifications for cascaded state changes
                    rootCause: "State changed by episode state cascade.",
                    stateChangeLog: undefined,
                    props: props,
                });
            }
            catch (error) {
                logger.error(`Failed to cascade state change to alert ${member.alertId.toString()}: ${error}`, {
                    projectId: projectId.toString(),
                    alertEpisodeId: episodeId.toString(),
                    alertId: member.alertId.toString(),
                });
            }
        }
    }
    async acknowledgeEpisode(episodeId, acknowledgedByUserId, cascadeToAlerts = true) {
        const episode = await this.findOneById({
            id: episodeId,
            select: {
                projectId: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!episode || !episode.projectId) {
            throw new BadDataException("Episode not found.");
        }
        const alertState = await AlertStateService.findOneBy({
            query: {
                projectId: episode.projectId,
                isAcknowledgedState: true,
            },
            select: {
                _id: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!alertState || !alertState.id) {
            throw new BadDataException("Acknowledged state not found for this project.");
        }
        await this.changeEpisodeState({
            projectId: episode.projectId,
            episodeId: episodeId,
            alertStateId: alertState.id,
            notifyOwners: true,
            rootCause: undefined,
            props: {
                isRoot: true,
                userId: acknowledgedByUserId,
            },
            cascadeToAlerts: cascadeToAlerts,
        });
        // Create feed for episode acknowledged
        let feedMessage = "Episode has been acknowledged.";
        if (cascadeToAlerts) {
            feedMessage += " All member alerts have also been acknowledged.";
        }
        await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
            alertEpisodeId: episodeId,
            projectId: episode.projectId,
            alertEpisodeFeedEventType: AlertEpisodeFeedEventType.EpisodeStateChanged,
            displayColor: Yellow500,
            feedInfoInMarkdown: feedMessage,
            userId: acknowledgedByUserId || undefined,
        });
    }
    async resolveEpisode(episodeId, resolvedByUserId, cascadeToAlerts = true) {
        const episode = await this.findOneById({
            id: episodeId,
            select: {
                projectId: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!episode || !episode.projectId) {
            throw new BadDataException("Episode not found.");
        }
        const alertState = await AlertStateService.findOneBy({
            query: {
                projectId: episode.projectId,
                isResolvedState: true,
            },
            select: {
                _id: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!alertState || !alertState.id) {
            throw new BadDataException("Resolved state not found for this project.");
        }
        await this.changeEpisodeState({
            projectId: episode.projectId,
            episodeId: episodeId,
            alertStateId: alertState.id,
            notifyOwners: true,
            rootCause: undefined,
            props: {
                isRoot: true,
                userId: resolvedByUserId,
            },
            cascadeToAlerts: cascadeToAlerts,
        });
        // Create feed for episode resolved
        let feedMessage = "Episode has been resolved.";
        if (cascadeToAlerts) {
            feedMessage += " All member alerts have also been resolved.";
        }
        await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
            alertEpisodeId: episodeId,
            projectId: episode.projectId,
            alertEpisodeFeedEventType: AlertEpisodeFeedEventType.EpisodeStateChanged,
            displayColor: Green500,
            feedInfoInMarkdown: feedMessage,
            userId: resolvedByUserId || undefined,
        });
    }
    async updateEpisodeSeverity(data) {
        var _a;
        const { episodeId, severityId, onlyIfHigher } = data;
        const episode = await this.findOneById({
            id: episodeId,
            select: {
                alertSeverityId: true,
                alertSeverity: {
                    order: true,
                },
            },
            props: {
                isRoot: true,
            },
        });
        if (!episode) {
            throw new BadDataException("Episode not found.");
        }
        if (onlyIfHigher && ((_a = episode.alertSeverity) === null || _a === void 0 ? void 0 : _a.order) !== undefined) {
            // Get the new severity to check its order
            const newSeverity = await AlertSeverityService.findOneById({
                id: severityId,
                select: {
                    order: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (newSeverity && newSeverity.order !== undefined) {
                // Lower order = higher severity
                if (newSeverity.order >= episode.alertSeverity.order) {
                    return; // Don't update if new severity is not higher
                }
            }
        }
        await this.updateOneById({
            id: episodeId,
            data: {
                alertSeverityId: severityId,
            },
            props: {
                isRoot: true,
            },
        });
    }
    async updateAlertCount(episodeId) {
        const count = await AlertEpisodeMemberService.countBy({
            query: {
                alertEpisodeId: episodeId,
            },
            props: {
                isRoot: true,
            },
        });
        const alertCount = count.toNumber();
        // Get the episode to check for templates
        const episode = await this.findOneById({
            id: episodeId,
            select: {
                titleTemplate: true,
                descriptionTemplate: true,
                title: true,
                description: true,
            },
            props: {
                isRoot: true,
            },
        });
        const updateData = {
            alertCount: alertCount,
        };
        // Update title with dynamic variables if template exists
        if (episode === null || episode === void 0 ? void 0 : episode.titleTemplate) {
            updateData.title = this.renderTemplateWithDynamicValues(episode.titleTemplate, alertCount);
        }
        // Update description with dynamic variables if template exists
        if (episode === null || episode === void 0 ? void 0 : episode.descriptionTemplate) {
            updateData.description = this.renderTemplateWithDynamicValues(episode.descriptionTemplate, alertCount);
        }
        await this.updateOneById({
            id: episodeId,
            data: updateData,
            props: {
                isRoot: true,
            },
        });
    }
    renderTemplateWithDynamicValues(template, alertCount) {
        let result = template;
        // Replace dynamic variables
        result = result.replace(/\{\{alertCount\}\}/g, alertCount.toString());
        return result;
    }
    async updateLastAlertAddedAt(episodeId) {
        await this.updateOneById({
            id: episodeId,
            data: {
                lastAlertAddedAt: OneUptimeDate.getCurrentDate(),
            },
            props: {
                isRoot: true,
            },
        });
    }
    async isEpisodeResolved(episodeId) {
        var _a;
        const episode = await this.findOneById({
            id: episodeId,
            select: {
                projectId: true,
                currentAlertState: {
                    order: true,
                },
            },
            props: {
                isRoot: true,
            },
        });
        if (!episode || !episode.projectId) {
            throw new BadDataException("Episode not found.");
        }
        const resolvedState = await AlertStateService.getResolvedAlertState({
            projectId: episode.projectId,
            props: {
                isRoot: true,
            },
        });
        const currentOrder = ((_a = episode.currentAlertState) === null || _a === void 0 ? void 0 : _a.order) || 0;
        const resolvedOrder = resolvedState.order || 0;
        return currentOrder >= resolvedOrder;
    }
    async isEpisodeAcknowledged(data) {
        var _a;
        const episode = await this.findOneById({
            id: data.episodeId,
            select: {
                projectId: true,
                currentAlertState: {
                    order: true,
                },
            },
            props: {
                isRoot: true,
            },
        });
        if (!episode || !episode.projectId) {
            throw new BadDataException("Episode not found.");
        }
        const acknowledgedState = await AlertStateService.getAcknowledgedAlertState({
            projectId: episode.projectId,
            props: {
                isRoot: true,
            },
        });
        const currentOrder = ((_a = episode.currentAlertState) === null || _a === void 0 ? void 0 : _a.order) || 0;
        const acknowledgedOrder = acknowledgedState.order || 0;
        return currentOrder >= acknowledgedOrder;
    }
    async reopenEpisode(episodeId, reopenedByUserId) {
        const episode = await this.findOneById({
            id: episodeId,
            select: {
                projectId: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!episode || !episode.projectId) {
            throw new BadDataException("Episode not found.");
        }
        // Get the created state
        const createdState = await AlertStateService.findOneBy({
            query: {
                projectId: episode.projectId,
                isCreatedState: true,
            },
            select: {
                _id: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!createdState || !createdState.id) {
            throw new BadDataException("Created state not found for this project.");
        }
        await this.changeEpisodeState({
            projectId: episode.projectId,
            episodeId: episodeId,
            alertStateId: createdState.id,
            notifyOwners: true,
            rootCause: "Episode reopened due to new alert added.",
            props: {
                isRoot: true,
                userId: reopenedByUserId,
            },
        });
        // Clear resolved timestamp and allAlertsResolvedAt
        await this.updateOneById({
            id: episodeId,
            data: {
                resolvedAt: null,
                allAlertsResolvedAt: null,
            },
            props: {
                isRoot: true,
            },
        });
    }
    async getEpisodeLinkInDashboard(projectId, episodeId) {
        const dashboardUrl = await DatabaseConfig.getDashboardUrl();
        return URL.fromString(dashboardUrl.toString()).addRoute(`/${projectId.toString()}/alerts/episodes/${episodeId.toString()}`);
    }
    async getEpisodeNumber(data) {
        const episode = await this.findOneById({
            id: data.episodeId,
            select: {
                episodeNumber: true,
                episodeNumberWithPrefix: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!episode) {
            throw new BadDataException("Episode not found.");
        }
        return {
            number: episode.episodeNumber ? Number(episode.episodeNumber) : null,
            numberWithPrefix: episode.episodeNumberWithPrefix || null,
        };
    }
    async onUpdateSuccess(onUpdate, updatedItemIds) {
        // Handle state changes
        if (onUpdate.updateBy.data.currentAlertStateId &&
            onUpdate.updateBy.props.tenantId) {
            for (const itemId of updatedItemIds) {
                await this.changeEpisodeState({
                    projectId: onUpdate.updateBy.props.tenantId,
                    episodeId: itemId,
                    alertStateId: onUpdate.updateBy.data.currentAlertStateId,
                    notifyOwners: true,
                    rootCause: "State was changed when the episode was updated.",
                    props: {
                        isRoot: true,
                    },
                });
            }
        }
        return onUpdate;
    }
    async findOwners(episodeId) {
        // Get direct user owners
        const ownerUsers = await AlertEpisodeOwnerUserService.findBy({
            query: {
                alertEpisodeId: episodeId,
            },
            select: {
                _id: true,
                user: {
                    _id: true,
                    email: true,
                    name: true,
                    timezone: true,
                },
            },
            props: {
                isRoot: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
        });
        // Get team owners
        const ownerTeams = await AlertEpisodeOwnerTeamService.findBy({
            query: {
                alertEpisodeId: episodeId,
            },
            select: {
                _id: true,
                teamId: true,
            },
            props: {
                isRoot: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
        });
        const users = ownerUsers
            .map((ownerUser) => {
            return ownerUser.user;
        })
            .filter((user) => {
            return Boolean(user);
        });
        // Expand teams to individual users
        if (ownerTeams.length > 0) {
            const teamIds = ownerTeams.map((ownerTeam) => {
                return ownerTeam.teamId;
            });
            const teamUsers = await TeamMemberService.getUsersInTeams(teamIds);
            for (const teamUser of teamUsers) {
                // Avoid duplicates
                if (!users.find((user) => {
                    var _a, _b;
                    return ((_a = user.id) === null || _a === void 0 ? void 0 : _a.toString()) === ((_b = teamUser.id) === null || _b === void 0 ? void 0 : _b.toString());
                })) {
                    users.push(teamUser);
                }
            }
        }
        return users;
    }
    async getWorkspaceChannelForEpisode(data) {
        const episode = await this.findOneById({
            id: data.episodeId,
            select: {
                postUpdatesToWorkspaceChannels: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!episode) {
            throw new BadDataException("Alert Episode not found.");
        }
        return (episode.postUpdatesToWorkspaceChannels || []).filter((channel) => {
            if (!data.workspaceType) {
                return true;
            }
            return channel.workspaceType === data.workspaceType;
        });
    }
    async addOwners(projectId, episodeId, userIds, teamIds, notifyOwners, props) {
        for (let teamId of teamIds) {
            if (typeof teamId === Typeof.String) {
                teamId = new ObjectID(teamId.toString());
            }
            const teamOwner = new AlertEpisodeOwnerTeam();
            teamOwner.alertEpisodeId = episodeId;
            teamOwner.projectId = projectId;
            teamOwner.teamId = teamId;
            teamOwner.isOwnerNotified = !notifyOwners;
            await AlertEpisodeOwnerTeamService.create({
                data: teamOwner,
                props: props,
            });
        }
        for (let userId of userIds) {
            if (typeof userId === Typeof.String) {
                userId = new ObjectID(userId.toString());
            }
            const userOwner = new AlertEpisodeOwnerUser();
            userOwner.alertEpisodeId = episodeId;
            userOwner.projectId = projectId;
            userOwner.userId = userId;
            userOwner.isOwnerNotified = !notifyOwners;
            await AlertEpisodeOwnerUserService.create({
                data: userOwner,
                props: props,
            });
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeFind", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "countBy", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeUpdate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeDelete", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeCreate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Model]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onCreateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Model]),
    __metadata("design:returntype", Promise)
], Service.prototype, "handleEpisodeWorkspaceOperationsAsync", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Model]),
    __metadata("design:returntype", Promise)
], Service.prototype, "createEpisodeCreatedFeed", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Model]),
    __metadata("design:returntype", Promise)
], Service.prototype, "executeEpisodeOnCallDutyPoliciesAsync", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "changeEpisodeState", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "cascadeStateToMemberAlerts", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID, Boolean]),
    __metadata("design:returntype", Promise)
], Service.prototype, "acknowledgeEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID, Boolean]),
    __metadata("design:returntype", Promise)
], Service.prototype, "resolveEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "updateEpisodeSeverity", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "updateAlertCount", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "updateLastAlertAddedAt", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "isEpisodeResolved", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "isEpisodeAcknowledged", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "reopenEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getEpisodeLinkInDashboard", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getEpisodeNumber", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Array]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onUpdateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "findOwners", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getWorkspaceChannelForEpisode", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID,
        Array,
        Array, Boolean, Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "addOwners", null);
export default new Service();
//# sourceMappingURL=AlertEpisodeService.js.map