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 QueryHelper from "../Types/Database/QueryHelper";
import DatabaseService from "./DatabaseService";
import IncidentPublicNoteService from "./IncidentPublicNoteService";
import IncidentService from "./IncidentService";
import IncidentSlaService from "./IncidentSlaService";
import IncidentStateService from "./IncidentStateService";
import UserService from "./UserService";
import IncidentMemberService from "./IncidentMemberService";
import IncidentRoleService from "./IncidentRoleService";
import SortOrder from "../../Types/BaseDatabase/SortOrder";
import OneUptimeDate from "../../Types/Date";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import StatusPageSubscriberNotificationStatus from "../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
import IncidentPublicNote from "../../Models/DatabaseModels/IncidentPublicNote";
import IncidentStateTimeline from "../../Models/DatabaseModels/IncidentStateTimeline";
import IncidentMember from "../../Models/DatabaseModels/IncidentMember";
import { IsBillingEnabled } from "../EnvironmentConfig";
import logger from "../Utils/Logger";
import IncidentFeedService from "./IncidentFeedService";
import { IncidentFeedEventType } from "../../Models/DatabaseModels/IncidentFeed";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
import Semaphore from "../Infrastructure/Semaphore";
export class Service extends DatabaseService {
    constructor() {
        super(IncidentStateTimeline);
        if (IsBillingEnabled) {
            this.hardDeleteItemsOlderThanInDays("startsAt", 3 * 365); // 3 years
        }
    }
    async getResolvedStateIdForProject(projectId) {
        const resolvedState = await IncidentStateService.findOneBy({
            query: {
                projectId: projectId,
                isResolvedState: true,
            },
            props: {
                isRoot: true,
            },
            select: {
                _id: true,
            },
        });
        if (!resolvedState) {
            throw new BadDataException("No resolved state found for the project");
        }
        return resolvedState.id;
    }
    async onBeforeCreate(createBy) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
        let mutex = null;
        try {
            if (!createBy.data.incidentId) {
                throw new BadDataException("incidentId is null");
            }
            try {
                mutex = await Semaphore.lock({
                    key: createBy.data.incidentId.toString(),
                    namespace: "IncidentStateTimeline.create",
                });
            }
            catch (err) {
                logger.error(err, {
                    projectId: (_a = createBy.data.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    incidentId: (_b = createBy.data.incidentId) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
            if (!createBy.data.startsAt) {
                createBy.data.startsAt = OneUptimeDate.getCurrentDate();
            }
            if ((createBy.data.createdByUserId ||
                createBy.data.createdByUser ||
                createBy.props.userId) &&
                !createBy.data.rootCause) {
                let userId = createBy.data.createdByUserId;
                if (createBy.props.userId) {
                    userId = createBy.props.userId;
                }
                if (createBy.data.createdByUser && createBy.data.createdByUser.id) {
                    userId = createBy.data.createdByUser.id;
                }
                if (userId) {
                    createBy.data.rootCause = `Incident state created by ${await UserService.getUserMarkdownString({
                        userId: userId,
                        projectId: createBy.data.projectId || createBy.props.tenantId,
                    })}`;
                }
            }
            const incidentStateId = createBy.data.incidentStateId || ((_c = createBy.data.incidentState) === null || _c === void 0 ? void 0 : _c.id);
            if (!incidentStateId) {
                throw new BadDataException("incidentStateId is null");
            }
            // Execute queries for before and after states in parallel for better performance
            const [stateBeforeThis, stateAfterThis] = await Promise.all([
                this.findOneBy({
                    query: {
                        incidentId: createBy.data.incidentId,
                        startsAt: QueryHelper.lessThanEqualTo(createBy.data.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Descending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        incidentStateId: true,
                        incidentState: {
                            _id: true,
                            order: true,
                            name: true,
                            isResolvedState: true,
                        },
                        startsAt: true,
                        endsAt: true,
                    },
                }),
                this.findOneBy({
                    query: {
                        incidentId: createBy.data.incidentId,
                        startsAt: QueryHelper.greaterThan(createBy.data.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Ascending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        incidentStateId: true,
                        startsAt: true,
                        endsAt: true,
                    },
                }),
            ]);
            logger.debug("State Before this", {
                projectId: (_d = createBy.data.projectId) === null || _d === void 0 ? void 0 : _d.toString(),
                incidentId: (_e = createBy.data.incidentId) === null || _e === void 0 ? void 0 : _e.toString(),
            });
            logger.debug(stateBeforeThis, {
                projectId: (_f = createBy.data.projectId) === null || _f === void 0 ? void 0 : _f.toString(),
                incidentId: (_g = createBy.data.incidentId) === null || _g === void 0 ? void 0 : _g.toString(),
            });
            // If this is the first state, then do not notify the owner.
            if (!stateBeforeThis) {
                // since this is the first status, do not notify the owner.
                createBy.data.isOwnerNotified = true;
            }
            /*
             * check if this new state and the previous state are same.
             * if yes, then throw bad data exception.
             */
            if (stateBeforeThis &&
                stateBeforeThis.incidentStateId &&
                incidentStateId) {
                if (stateBeforeThis.incidentStateId.toString() ===
                    incidentStateId.toString()) {
                    throw new BadDataException("Incident state cannot be same as previous state.");
                }
            }
            if (stateBeforeThis && ((_h = stateBeforeThis.incidentState) === null || _h === void 0 ? void 0 : _h.order)) {
                const newIncidentState = await IncidentStateService.findOneBy({
                    query: {
                        _id: incidentStateId,
                    },
                    select: {
                        order: true,
                        name: true,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (newIncidentState && newIncidentState.order) {
                    // check if the new incident state is in order is greater than the previous state order
                    if (stateBeforeThis &&
                        stateBeforeThis.incidentState &&
                        stateBeforeThis.incidentState.order &&
                        newIncidentState.order <= stateBeforeThis.incidentState.order) {
                        throw new BadDataException(`Incident cannot transition to ${newIncidentState.name} state from ${stateBeforeThis.incidentState.name} state because ${newIncidentState.name} is before ${stateBeforeThis.incidentState.name} in the order of incident states.`);
                    }
                }
            }
            // compute ends at. It's the start of the next status.
            if (stateAfterThis && stateAfterThis.startsAt) {
                createBy.data.endsAt = stateAfterThis.startsAt;
            }
            /*
             * check if this new state and the previous state are same.
             * if yes, then throw bad data exception.
             */
            if (stateAfterThis && stateAfterThis.incidentStateId && incidentStateId) {
                if (stateAfterThis.incidentStateId.toString() ===
                    incidentStateId.toString()) {
                    throw new BadDataException("Incident state cannot be same as next state.");
                }
            }
            logger.debug("State After this", {
                projectId: (_j = createBy.data.projectId) === null || _j === void 0 ? void 0 : _j.toString(),
                incidentId: (_k = createBy.data.incidentId) === null || _k === void 0 ? void 0 : _k.toString(),
            });
            logger.debug(stateAfterThis, {
                projectId: (_l = createBy.data.projectId) === null || _l === void 0 ? void 0 : _l.toString(),
                incidentId: (_m = createBy.data.incidentId) === null || _m === void 0 ? void 0 : _m.toString(),
            });
            const publicNote = (_o = createBy.miscDataProps) === null || _o === void 0 ? void 0 : _o["publicNote"];
            if (publicNote) {
                // mark status page subscribers as notified for this state change because we dont want to send duplicate (two) emails one for public note and one for state change.
                if (createBy.data.shouldStatusPageSubscribersBeNotified) {
                    createBy.data.subscriberNotificationStatus =
                        StatusPageSubscriberNotificationStatus.Success;
                }
            }
            // Set notification status based on shouldStatusPageSubscribersBeNotified
            if (createBy.data.shouldStatusPageSubscribersBeNotified === false) {
                createBy.data.subscriberNotificationStatus =
                    StatusPageSubscriberNotificationStatus.Skipped;
                createBy.data.subscriberNotificationStatusMessage =
                    "Notifications skipped as subscribers are not to be notified for this incident state change.";
            }
            else if (createBy.data.shouldStatusPageSubscribersBeNotified === true &&
                !publicNote) {
                // Only set to Pending if there's no public note (public note handling sets it to Success)
                createBy.data.subscriberNotificationStatus =
                    StatusPageSubscriberNotificationStatus.Pending;
            }
            return {
                createBy,
                carryForward: {
                    statusTimelineBeforeThisStatus: stateBeforeThis || null,
                    statusTimelineAfterThisStatus: stateAfterThis || null,
                    publicNote: publicNote,
                    mutex: mutex,
                },
            };
        }
        catch (err) {
            // release the mutex if it was acquired.
            if (mutex) {
                try {
                    await Semaphore.release(mutex);
                }
                catch (err) {
                    logger.error(err, {
                        projectId: (_p = createBy.data.projectId) === null || _p === void 0 ? void 0 : _p.toString(),
                        incidentId: (_q = createBy.data.incidentId) === null || _q === void 0 ? void 0 : _q.toString(),
                    });
                }
            }
            throw err;
        }
    }
    async onCreateSuccess(onCreate, createdItem) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
        const mutex = onCreate.carryForward.mutex;
        if (!createdItem.incidentId) {
            throw new BadDataException("incidentId is null");
        }
        if (!createdItem.incidentStateId) {
            throw new BadDataException("incidentStateId is null");
        }
        // update the last status as ended.
        logger.debug("Status Timeline Before this", {
            projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
            incidentId: (_b = createdItem.incidentId) === null || _b === void 0 ? void 0 : _b.toString(),
        });
        logger.debug(onCreate.carryForward.statusTimelineBeforeThisStatus, {
            projectId: (_c = createdItem.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
            incidentId: (_d = createdItem.incidentId) === null || _d === void 0 ? void 0 : _d.toString(),
        });
        logger.debug("Status Timeline After this", {
            projectId: (_e = createdItem.projectId) === null || _e === void 0 ? void 0 : _e.toString(),
            incidentId: (_f = createdItem.incidentId) === null || _f === void 0 ? void 0 : _f.toString(),
        });
        logger.debug(onCreate.carryForward.statusTimelineAfterThisStatus, {
            projectId: (_g = createdItem.projectId) === null || _g === void 0 ? void 0 : _g.toString(),
            incidentId: (_h = createdItem.incidentId) === null || _h === void 0 ? void 0 : _h.toString(),
        });
        logger.debug("Created Item", {
            projectId: (_j = createdItem.projectId) === null || _j === void 0 ? void 0 : _j.toString(),
            incidentId: (_k = createdItem.incidentId) === null || _k === void 0 ? void 0 : _k.toString(),
        });
        logger.debug(createdItem, {
            projectId: (_l = createdItem.projectId) === null || _l === void 0 ? void 0 : _l.toString(),
            incidentId: (_m = createdItem.incidentId) === null || _m === void 0 ? void 0 : _m.toString(),
        });
        /*
         * now there are three cases.
         * 1. This is the first status OR there's no status after this.
         */
        if (!onCreate.carryForward.statusTimelineBeforeThisStatus) {
            // This is the first status, no need to update previous status.
            logger.debug("This is the first status.", {
                projectId: (_o = createdItem.projectId) === null || _o === void 0 ? void 0 : _o.toString(),
                incidentId: (_p = createdItem.incidentId) === null || _p === void 0 ? void 0 : _p.toString(),
            });
        }
        else if (!onCreate.carryForward.statusTimelineAfterThisStatus) {
            /*
             * 2. This is the last status.
             * Update the previous status to end at the start of this status.
             */
            await this.updateOneById({
                id: onCreate.carryForward.statusTimelineBeforeThisStatus.id,
                data: {
                    endsAt: createdItem.startsAt,
                },
                props: {
                    isRoot: true,
                },
            });
            logger.debug("This is the last status.", {
                projectId: (_q = createdItem.projectId) === null || _q === void 0 ? void 0 : _q.toString(),
                incidentId: (_r = createdItem.incidentId) === null || _r === void 0 ? void 0 : _r.toString(),
            });
        }
        else {
            /*
             * 3. This is in the middle.
             * Update the previous status to end at the start of this status.
             */
            await this.updateOneById({
                id: onCreate.carryForward.statusTimelineBeforeThisStatus.id,
                data: {
                    endsAt: createdItem.startsAt,
                },
                props: {
                    isRoot: true,
                },
            });
            // Update the next status to start at the end of this status.
            await this.updateOneById({
                id: onCreate.carryForward.statusTimelineAfterThisStatus.id,
                data: {
                    startsAt: createdItem.endsAt,
                },
                props: {
                    isRoot: true,
                },
            });
            logger.debug("This status is in the middle.", {
                projectId: (_s = createdItem.projectId) === null || _s === void 0 ? void 0 : _s.toString(),
                incidentId: (_t = createdItem.incidentId) === null || _t === void 0 ? void 0 : _t.toString(),
            });
        }
        if (!createdItem.endsAt) {
            await IncidentService.updateOneBy({
                query: {
                    _id: (_u = createdItem.incidentId) === null || _u === void 0 ? void 0 : _u.toString(),
                },
                data: {
                    currentIncidentStateId: createdItem.incidentStateId,
                },
                props: onCreate.createBy.props,
            });
        }
        const incidentState = await IncidentStateService.findOneBy({
            query: {
                _id: createdItem.incidentStateId.toString(),
            },
            props: {
                isRoot: true,
            },
            select: {
                _id: true,
                isResolvedState: true,
                isAcknowledgedState: true,
                isCreatedState: true,
                color: true,
                name: true,
            },
        });
        if (mutex) {
            try {
                await Semaphore.release(mutex);
            }
            catch (err) {
                logger.error(err, {
                    projectId: (_v = createdItem.projectId) === null || _v === void 0 ? void 0 : _v.toString(),
                    incidentId: (_w = createdItem.incidentId) === null || _w === void 0 ? void 0 : _w.toString(),
                });
            }
        }
        const stateName = (incidentState === null || incidentState === void 0 ? void 0 : incidentState.name) || "";
        let stateEmoji = "➡️";
        // if resolved state then change emoji to ✅.
        if (incidentState === null || incidentState === void 0 ? void 0 : incidentState.isResolvedState) {
            stateEmoji = "✅";
        }
        else if (incidentState === null || incidentState === void 0 ? void 0 : incidentState.isAcknowledgedState) {
            // eyes emoji for acknowledged state.
            stateEmoji = "👀";
        }
        else if (incidentState === null || incidentState === void 0 ? void 0 : incidentState.isCreatedState) {
            stateEmoji = "🔴";
        }
        const incidentNumberResult = await IncidentService.getIncidentNumber({
            incidentId: createdItem.incidentId,
        });
        const incidentNumberDisplay = incidentNumberResult.numberWithPrefix ||
            "#" + incidentNumberResult.number;
        const projectId = createdItem.projectId;
        const incidentId = createdItem.incidentId;
        await IncidentFeedService.createIncidentFeedItem({
            incidentId: createdItem.incidentId,
            projectId: createdItem.projectId,
            incidentFeedEventType: IncidentFeedEventType.IncidentStateChanged,
            displayColor: incidentState === null || incidentState === void 0 ? void 0 : incidentState.color,
            feedInfoInMarkdown: stateEmoji +
                ` Changed **[Incident ${incidentNumberDisplay}](${(await IncidentService.getIncidentLinkInDashboard(projectId, incidentId)).toString()}) State** to **` +
                stateName +
                "**",
            moreInformationInMarkdown: `**Cause:**
${createdItem.rootCause}`,
            userId: createdItem.createdByUserId || onCreate.createBy.props.userId,
            workspaceNotification: {
                sendWorkspaceNotification: true,
                notifyUserId: createdItem.createdByUserId || onCreate.createBy.props.userId,
            },
        });
        // Auto-assign Incident Commander if not already assigned
        const stateChangeUserId = createdItem.createdByUserId || onCreate.createBy.props.userId;
        if (stateChangeUserId) {
            this.autoAssignIncidentCommander({
                incidentId: createdItem.incidentId,
                projectId: createdItem.projectId,
                userId: stateChangeUserId,
            }).catch((error) => {
                var _a, _b, _c, _d;
                logger.error(`Error while auto-assigning incident commander:`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    incidentId: (_b = createdItem.incidentId) === null || _b === void 0 ? void 0 : _b.toString(),
                });
                logger.error(error, {
                    projectId: (_c = createdItem.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
                    incidentId: (_d = createdItem.incidentId) === null || _d === void 0 ? void 0 : _d.toString(),
                });
            });
        }
        const isResolvedState = (incidentState === null || incidentState === void 0 ? void 0 : incidentState.isResolvedState) || false;
        if (isResolvedState) {
            const incident = await IncidentService.findOneBy({
                query: {
                    _id: createdItem.incidentId.toString(),
                },
                select: {
                    _id: true,
                    projectId: true,
                    monitors: {
                        _id: true,
                    },
                },
                props: {
                    isRoot: true,
                },
            });
            if (incident) {
                await IncidentService.markMonitorsActiveForMonitoring(incident.projectId, incident.monitors || [], createdItem.startsAt || undefined);
            }
        }
        if (onCreate.carryForward.publicNote) {
            const publicNote = onCreate.carryForward.publicNote;
            const incidentPublicNote = new IncidentPublicNote();
            incidentPublicNote.incidentId = createdItem.incidentId;
            incidentPublicNote.note = publicNote;
            incidentPublicNote.postedAt = createdItem.startsAt;
            incidentPublicNote.createdAt = createdItem.startsAt;
            incidentPublicNote.projectId = createdItem.projectId;
            incidentPublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated =
                Boolean(createdItem.shouldStatusPageSubscribersBeNotified);
            await IncidentPublicNoteService.create({
                data: incidentPublicNote,
                props: onCreate.createBy.props,
            });
        }
        IncidentService.refreshIncidentMetrics({
            incidentId: createdItem.incidentId,
        }).catch((error) => {
            var _a, _b, _c, _d;
            logger.error(`Error while refreshing incident metrics:`, {
                projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                incidentId: (_b = createdItem.incidentId) === null || _b === void 0 ? void 0 : _b.toString(),
            });
            logger.error(error, {
                projectId: (_c = createdItem.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
                incidentId: (_d = createdItem.incidentId) === null || _d === void 0 ? void 0 : _d.toString(),
            });
        });
        // Track SLA response/resolution times
        this.trackSlaStateChange({
            incidentId: createdItem.incidentId,
            projectId: createdItem.projectId,
            isAcknowledgedState: (incidentState === null || incidentState === void 0 ? void 0 : incidentState.isAcknowledgedState) || false,
            isResolvedState: (incidentState === null || incidentState === void 0 ? void 0 : incidentState.isResolvedState) || false,
            stateChangedAt: createdItem.startsAt || OneUptimeDate.getCurrentDate(),
            previousStateWasResolved: ((_y = (_x = onCreate.carryForward.statusTimelineBeforeThisStatus) === null || _x === void 0 ? void 0 : _x.incidentState) === null || _y === void 0 ? void 0 : _y.isResolvedState) || false,
        }).catch((error) => {
            var _a, _b, _c, _d;
            logger.error(`Error while tracking SLA state change:`, {
                projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                incidentId: (_b = createdItem.incidentId) === null || _b === void 0 ? void 0 : _b.toString(),
            });
            logger.error(error, {
                projectId: (_c = createdItem.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
                incidentId: (_d = createdItem.incidentId) === null || _d === void 0 ? void 0 : _d.toString(),
            });
        });
        const isLastIncidentState = await this.isLastIncidentState({
            projectId: createdItem.projectId,
            incidentStateId: createdItem.incidentStateId,
        });
        if (isLastIncidentState) {
            WorkspaceNotificationRuleService.archiveWorkspaceChannels({
                projectId: createdItem.projectId,
                notificationFor: {
                    incidentId: createdItem.incidentId,
                },
                sendMessageBeforeArchiving: {
                    _type: "WorkspacePayloadMarkdown",
                    text: `**[Incident ${incidentNumberDisplay}](${(await IncidentService.getIncidentLinkInDashboard(createdItem.projectId, createdItem.incidentId)).toString()})** is resolved. Archiving channel.`,
                },
            }).catch((error) => {
                var _a, _b, _c, _d;
                logger.error(`Error while archiving workspace channels:`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    incidentId: (_b = createdItem.incidentId) === null || _b === void 0 ? void 0 : _b.toString(),
                });
                logger.error(error, {
                    projectId: (_c = createdItem.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
                    incidentId: (_d = createdItem.incidentId) === null || _d === void 0 ? void 0 : _d.toString(),
                });
            });
        }
        return createdItem;
    }
    async isLastIncidentState(data) {
        var _a;
        // find all the states for this project and sort it by order. Then, check if this is the last state.
        const incidentStates = await IncidentStateService.findBy({
            query: {
                projectId: data.projectId,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            sort: {
                order: SortOrder.Ascending,
            },
            props: {
                isRoot: true,
            },
        });
        const incidentState = incidentStates.find((incidentState) => {
            var _a;
            return ((_a = incidentState.id) === null || _a === void 0 ? void 0 : _a.toString()) === data.incidentStateId.toString();
        }) || null;
        if (!incidentState) {
            throw new BadDataException("Incident state not found.");
        }
        const lastIncidentState = incidentStates[incidentStates.length - 1];
        if (lastIncidentState && lastIncidentState.id) {
            return lastIncidentState.id.toString() === ((_a = incidentState.id) === null || _a === void 0 ? void 0 : _a.toString());
        }
        return false;
    }
    async autoAssignIncidentCommander(data) {
        var _a, _b;
        // Find the primary role (Incident Commander) for this project
        const primaryRole = await IncidentRoleService.findOneBy({
            query: {
                projectId: data.projectId,
                isPrimaryRole: true,
            },
            select: {
                _id: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!primaryRole || !primaryRole.id) {
            // No primary role found for this project
            return;
        }
        // Check if there's already an Incident Commander assigned to this incident
        const existingCommander = await IncidentMemberService.findOneBy({
            query: {
                incidentId: data.incidentId,
                incidentRoleId: primaryRole.id,
            },
            select: {
                _id: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (existingCommander) {
            // Already has an Incident Commander, don't assign another one
            return;
        }
        // Check if this user is already assigned to the incident (with any role)
        const existingMembership = await IncidentMemberService.findOneBy({
            query: {
                incidentId: data.incidentId,
                userId: data.userId,
            },
            select: {
                _id: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (existingMembership) {
            // User is already assigned to this incident, don't assign again
            return;
        }
        // Assign the user as Incident Commander
        const incidentMember = new IncidentMember();
        incidentMember.incidentId = data.incidentId;
        incidentMember.projectId = data.projectId;
        incidentMember.userId = data.userId;
        incidentMember.incidentRoleId = primaryRole.id;
        await IncidentMemberService.create({
            data: incidentMember,
            props: {
                isRoot: true,
            },
        });
        logger.debug(`Auto-assigned user ${data.userId.toString()} as Incident Commander for incident ${data.incidentId.toString()}`, {
            projectId: (_a = data.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
            incidentId: (_b = data.incidentId) === null || _b === void 0 ? void 0 : _b.toString(),
        });
    }
    async trackSlaStateChange(data) {
        var _a, _b, _c, _d;
        try {
            // Check if incident is being reopened (previous state was resolved, current state is not resolved)
            if (data.previousStateWasResolved && !data.isResolvedState) {
                // Incident is being reopened - create a new SLA record
                const incident = await IncidentService.findOneById({
                    id: data.incidentId,
                    select: {
                        declaredAt: true,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (incident && incident.declaredAt) {
                    // Create a new SLA record starting from the reopen time
                    await IncidentSlaService.createSlaForIncident({
                        incidentId: data.incidentId,
                        projectId: data.projectId,
                        declaredAt: data.stateChangedAt, // Use reopen time as SLA start time
                    });
                    logger.info(`Created new SLA record for reopened incident ${data.incidentId}`, {
                        projectId: (_a = data.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                        incidentId: (_b = data.incidentId) === null || _b === void 0 ? void 0 : _b.toString(),
                    });
                }
                return;
            }
            // Track acknowledged state
            if (data.isAcknowledgedState) {
                await IncidentSlaService.markResponded({
                    incidentId: data.incidentId,
                    respondedAt: data.stateChangedAt,
                });
            }
            // Track resolved state
            if (data.isResolvedState) {
                await IncidentSlaService.markResolved({
                    incidentId: data.incidentId,
                    resolvedAt: data.stateChangedAt,
                });
            }
        }
        catch (error) {
            logger.error(`Error in trackSlaStateChange: ${error}`, {
                projectId: (_c = data.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
                incidentId: (_d = data.incidentId) === null || _d === void 0 ? void 0 : _d.toString(),
            });
            throw error;
        }
    }
    async onBeforeDelete(deleteBy) {
        if (deleteBy.query._id) {
            const incidentStateTimelineToBeDeleted = await this.findOneById({
                id: new ObjectID(deleteBy.query._id),
                select: {
                    incidentId: true,
                    startsAt: true,
                    endsAt: true,
                },
                props: {
                    isRoot: true,
                },
            });
            const incidentId = incidentStateTimelineToBeDeleted === null || incidentStateTimelineToBeDeleted === void 0 ? void 0 : incidentStateTimelineToBeDeleted.incidentId;
            if (incidentId) {
                const incidentStateTimeline = await this.countBy({
                    query: {
                        incidentId: incidentId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (!incidentStateTimelineToBeDeleted) {
                    throw new BadDataException("Incident state timeline not found.");
                }
                if (incidentStateTimeline.isOne()) {
                    throw new BadDataException("Cannot delete the only state timeline. Incident should have at least one state in its timeline.");
                }
                /*
                 * There are three cases.
                 * 1. This is the first state.
                 * 2. This is the last state.
                 * 3. This is in the middle.
                 */
                const stateBeforeThis = await this.findOneBy({
                    query: {
                        _id: QueryHelper.notEquals(deleteBy.query._id),
                        incidentId: incidentId,
                        startsAt: QueryHelper.lessThanEqualTo(incidentStateTimelineToBeDeleted.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Descending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        incidentStateId: true,
                        startsAt: true,
                        endsAt: true,
                    },
                });
                const stateAfterThis = await this.findOneBy({
                    query: {
                        incidentId: incidentId,
                        startsAt: QueryHelper.greaterThan(incidentStateTimelineToBeDeleted.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Ascending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        incidentStateId: true,
                        startsAt: true,
                        endsAt: true,
                    },
                });
                if (!stateBeforeThis) {
                    // This is the first state, no need to update previous state.
                    logger.debug("This is the first state.", {
                        incidentId: incidentId === null || incidentId === void 0 ? void 0 : incidentId.toString(),
                    });
                }
                else if (!stateAfterThis) {
                    /*
                     * This is the last state.
                     * Update the previous state to end at the end of this state.
                     */
                    await this.updateOneById({
                        id: stateBeforeThis.id,
                        data: {
                            endsAt: incidentStateTimelineToBeDeleted.endsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    logger.debug("This is the last state.", {
                        incidentId: incidentId === null || incidentId === void 0 ? void 0 : incidentId.toString(),
                    });
                }
                else {
                    /*
                     * This state is in the middle.
                     * Update the previous state to end at the start of the next state.
                     */
                    await this.updateOneById({
                        id: stateBeforeThis.id,
                        data: {
                            endsAt: stateAfterThis.startsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    // Update the next state to start at the start of this state.
                    await this.updateOneById({
                        id: stateAfterThis.id,
                        data: {
                            startsAt: incidentStateTimelineToBeDeleted.startsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    logger.debug("This state is in the middle.", {
                        incidentId: incidentId === null || incidentId === void 0 ? void 0 : incidentId.toString(),
                    });
                }
            }
            return { deleteBy, carryForward: incidentId };
        }
        return { deleteBy, carryForward: null };
    }
    async onDeleteSuccess(onDelete, _itemIdsBeforeDelete) {
        if (onDelete.carryForward) {
            // this is incidentId.
            const incidentId = onDelete.carryForward;
            // get last status of this incident.
            const incidentStateTimeline = await this.findOneBy({
                query: {
                    incidentId: incidentId,
                },
                sort: {
                    startsAt: SortOrder.Descending,
                },
                props: {
                    isRoot: true,
                },
                select: {
                    _id: true,
                    incidentStateId: true,
                },
            });
            if (incidentStateTimeline && incidentStateTimeline.incidentStateId) {
                await IncidentService.updateOneBy({
                    query: {
                        _id: incidentId.toString(),
                    },
                    data: {
                        currentIncidentStateId: incidentStateTimeline.incidentStateId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
            }
        }
        return onDelete;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getResolvedStateIdForProject", 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, IncidentStateTimeline]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onCreateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "autoAssignIncidentCommander", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "trackSlaStateChange", 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, Array]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onDeleteSuccess", null);
export default new Service();
//# sourceMappingURL=IncidentStateTimelineService.js.map