/**
 * Hierarchy Escalation Service
 *
 * Handles escalation routing when normal reporting chains are disrupted.
 * Workers can bypass their Team Leader and report directly to the
 * Orchestrator when escalation conditions are met.
 *
 * Escalation conditions:
 * - TL session does not exist (inactive/crashed)
 * - TL has not responded for 15 minutes
 * - Security-flagged task requiring direct escalation
 *
 * @module services/hierarchy/hierarchy-escalation
 */
// =============================================================================
// Constants
// =============================================================================
/** Default timeout before considering a Team Leader unresponsive (ms) */
const TL_UNRESPONSIVE_TIMEOUT_MS = 15 * 60 * 1000;
// =============================================================================
// Service
// =============================================================================
/**
 * HierarchyEscalationService handles routing of messages when the normal
 * reporting chain is disrupted. It checks escalation conditions, routes
 * messages to parent nodes, and emits hierarchy:escalation events.
 *
 * Singleton pattern matching other Crewly services.
 */
export class HierarchyEscalationService {
    static instance = null;
    /** Tracks TL last response times for unresponsive detection */
    tlResponseTimes = new Map();
    /** Event bus reference for emitting events */
    eventBus = null;
    /** Configurable unresponsive timeout (default 15 minutes) */
    unresponsiveTimeoutMs = TL_UNRESPONSIVE_TIMEOUT_MS;
    constructor() { }
    /**
     * Get the singleton instance of HierarchyEscalationService.
     *
     * @returns The singleton instance
     */
    static getInstance() {
        if (!HierarchyEscalationService.instance) {
            HierarchyEscalationService.instance = new HierarchyEscalationService();
        }
        return HierarchyEscalationService.instance;
    }
    /**
     * Clear the singleton instance (for testing).
     */
    static clearInstance() {
        HierarchyEscalationService.instance = null;
    }
    /**
     * Set the EventBusService reference for emitting events.
     *
     * @param eventBus - The EventBusService instance
     */
    setEventBus(eventBus) {
        this.eventBus = eventBus;
    }
    /**
     * Override the default unresponsive timeout (for testing).
     *
     * @param timeoutMs - Timeout in milliseconds
     */
    setUnresponsiveTimeout(timeoutMs) {
        this.unresponsiveTimeoutMs = timeoutMs;
    }
    /**
     * Record that a Team Leader has responded (resets unresponsive timer).
     *
     * @param sessionName - The TL's session name
     */
    recordTLResponse(sessionName) {
        this.tlResponseTimes.set(sessionName, {
            sessionName,
            lastResponseAt: new Date().toISOString(),
        });
    }
    /**
     * Check whether escalation conditions are met for a given worker.
     * Inspects the worker's Team Leader status and responsiveness.
     *
     * @param worker - The worker member attempting to report
     * @param allMembers - All team members for hierarchy traversal
     * @param securityFlag - Whether this is a security-flagged escalation
     * @returns Check result indicating if escalation is needed and where to route
     */
    checkEscalationConditions(worker, allMembers, securityFlag) {
        // Security-flagged tasks always escalate to top
        if (securityFlag) {
            const root = this.findRoot(allMembers);
            return {
                shouldEscalate: true,
                reason: 'security',
                targetSession: root?.sessionName,
                targetMemberId: root?.id,
            };
        }
        // Find the worker's parent (Team Leader)
        const parent = this.findParent(worker, allMembers);
        if (!parent) {
            // No parent — worker is at root, no escalation needed
            return { shouldEscalate: false };
        }
        // Check if parent is inactive
        if (parent.agentStatus === 'inactive') {
            const grandparent = this.findParent(parent, allMembers);
            const target = grandparent ?? this.findRoot(allMembers);
            return {
                shouldEscalate: true,
                reason: 'tl_inactive',
                targetSession: target?.sessionName,
                targetMemberId: target?.id,
            };
        }
        // Check if parent is unresponsive (no response within timeout)
        if (this.isTLUnresponsive(parent.sessionName)) {
            const grandparent = this.findParent(parent, allMembers);
            const target = grandparent ?? this.findRoot(allMembers);
            return {
                shouldEscalate: true,
                reason: 'tl_unresponsive',
                targetSession: target?.sessionName,
                targetMemberId: target?.id,
            };
        }
        return { shouldEscalate: false };
    }
    /**
     * Route a message to the parent member in the hierarchy.
     * Traverses upward along the parentMemberId chain.
     *
     * @param member - The member whose parent to find
     * @param allMembers - All team members
     * @returns The parent member, or null if none exists
     */
    routeToParent(member, allMembers) {
        return this.findParent(member, allMembers);
    }
    /**
     * Handle an unresponsive Team Leader situation.
     * Finds the next available parent in the hierarchy and creates
     * an escalation message.
     *
     * @param tlSession - Session name of the unresponsive TL
     * @param worker - The worker who detected the issue
     * @param allMembers - All team members
     * @param taskId - Task ID being escalated
     * @param message - Human-readable escalation message
     * @param teamInfo - Team info for event emission
     * @returns The escalation message and target, or null if no escalation target
     */
    handleTLUnresponsive(tlSession, worker, allMembers, taskId, message, teamInfo) {
        // Find TL member by session name
        const tl = allMembers.find(m => m.sessionName === tlSession);
        if (!tl)
            return null;
        // Find the next parent up the chain
        const target = this.findParent(tl, allMembers);
        if (!target) {
            // TL has no parent — we're at root, can't escalate further
            return null;
        }
        const escalation = {
            type: 'escalation',
            taskId,
            reason: 'tl_unresponsive',
            message,
            originalAssignedTo: tl.id,
            escalatedBy: worker.sessionName,
            timestamp: new Date().toISOString(),
        };
        // Emit hierarchy:escalation event
        if (this.eventBus && teamInfo) {
            const event = {
                id: crypto.randomUUID(),
                type: 'hierarchy:escalation',
                timestamp: new Date().toISOString(),
                teamId: teamInfo.teamId,
                teamName: teamInfo.teamName,
                memberId: worker.id,
                memberName: worker.name,
                sessionName: worker.sessionName,
                previousValue: tlSession,
                newValue: target.sessionName,
                changedField: 'hierarchyAction',
                taskId,
                hierarchyLevel: worker.hierarchyLevel,
                parentMemberId: worker.parentMemberId,
            };
            this.eventBus.publish(event);
        }
        return { escalation, target };
    }
    /**
     * Get the full escalation chain from a member up to root.
     * Returns members in order: [immediate parent, grandparent, ..., root].
     *
     * @param member - Starting member
     * @param allMembers - All team members
     * @returns Array of members from parent to root
     */
    getEscalationChain(member, allMembers) {
        const chain = [];
        const visited = new Set();
        let current = member;
        while (current.parentMemberId) {
            if (visited.has(current.id))
                break; // Cycle detected
            visited.add(current.id);
            const parent = allMembers.find(m => m.id === current.parentMemberId);
            if (!parent)
                break;
            chain.push(parent);
            current = parent;
        }
        return chain;
    }
    /**
     * Clear all TL response records (for cleanup/testing).
     */
    clearResponseRecords() {
        this.tlResponseTimes.clear();
    }
    // ===========================================================================
    // Private helpers
    // ===========================================================================
    /**
     * Find the parent member of a given member.
     */
    findParent(member, allMembers) {
        if (!member.parentMemberId)
            return null;
        return allMembers.find(m => m.id === member.parentMemberId) ?? null;
    }
    /**
     * Find the root member (no parentMemberId) in the hierarchy.
     */
    findRoot(allMembers) {
        return allMembers.find(m => !m.parentMemberId && m.hierarchyLevel === 0) ?? null;
    }
    /**
     * Check if a Team Leader has been unresponsive beyond the timeout.
     */
    isTLUnresponsive(sessionName) {
        const record = this.tlResponseTimes.get(sessionName);
        if (!record) {
            // No response ever recorded — not considered unresponsive
            // (TL may have just been assigned, hasn't had time to respond yet)
            return false;
        }
        const lastResponse = new Date(record.lastResponseAt).getTime();
        const elapsed = Date.now() - lastResponse;
        return elapsed >= this.unresponsiveTimeoutMs;
    }
}
//# sourceMappingURL=hierarchy-escalation.service.js.map