import { randomBytes } from 'crypto';
export class SessionManager {
    agents = new Map();
    sessionToAgent = new Map();
    webClients = new Map();
    sessionOwners = new Map();
    knownSessions = new Map();
    /** Reverse index: sessionId → Set of clientIds for O(1) lookup */
    sessionToClients = new Map();
    static SESSION_OWNER_TTL = 24 * 60 * 60 * 1000; // 24 hours
    static KNOWN_SESSION_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days
    onMutation;
    generateSessionId() {
        return randomBytes(12).toString('base64url');
    }
    // --- Agent operations ---
    registerAgent(agentId, agent) {
        this.agents.set(agentId, agent);
        this.sessionToAgent.set(agent.sessionId, agentId);
        this.knownSessions.set(agent.sessionId, {
            agentId,
            name: agent.name,
            hostname: agent.hostname,
            workDir: agent.workDir,
            version: agent.version,
            entra: agent.entra,
            lastSeen: Date.now(),
        });
        this.onMutation?.();
    }
    removeAgent(agentId) {
        const agent = this.agents.get(agentId);
        if (agent) {
            this.sessionToAgent.delete(agent.sessionId);
            this.agents.delete(agentId);
            // Update lastSeen in knownSessions (don't delete — persist across restart)
            const known = this.knownSessions.get(agent.sessionId);
            if (known) {
                known.lastSeen = Date.now();
                this.onMutation?.();
            }
        }
    }
    getAgent(agentId) {
        return this.agents.get(agentId);
    }
    getAgentBySession(sessionId) {
        const agentId = this.sessionToAgent.get(sessionId);
        return agentId ? this.agents.get(agentId) : undefined;
    }
    getKnownSession(sessionId) {
        return this.knownSessions.get(sessionId);
    }
    // --- Session owner operations (with persistence trigger) ---
    setSessionOwner(sessionId, oid, email) {
        this.sessionOwners.set(sessionId, { oid, email, updatedAt: Date.now() });
        this.onMutation?.();
    }
    removeSessionOwner(sessionId) {
        if (this.sessionOwners.has(sessionId)) {
            this.sessionOwners.delete(sessionId);
            this.onMutation?.();
        }
    }
    // --- Client operations ---
    registerClient(clientId, client) {
        this.webClients.set(clientId, client);
        // Maintain reverse index
        let set = this.sessionToClients.get(client.sessionId);
        if (!set) {
            set = new Set();
            this.sessionToClients.set(client.sessionId, set);
        }
        set.add(clientId);
    }
    removeClient(clientId) {
        const client = this.webClients.get(clientId);
        if (client) {
            // Maintain reverse index
            const set = this.sessionToClients.get(client.sessionId);
            if (set) {
                set.delete(clientId);
                if (set.size === 0)
                    this.sessionToClients.delete(client.sessionId);
            }
        }
        this.webClients.delete(clientId);
    }
    getClient(clientId) {
        return this.webClients.get(clientId);
    }
    getClientsForSession(sessionId) {
        const clientIds = this.sessionToClients.get(sessionId);
        if (!clientIds)
            return [];
        const result = [];
        for (const id of clientIds) {
            const c = this.webClients.get(id);
            if (c)
                result.push(c);
        }
        return result;
    }
    // --- Restore from persisted state ---
    loadSessionOwners(data) {
        for (const [sessionId, owner] of Object.entries(data)) {
            this.sessionOwners.set(sessionId, owner);
        }
    }
    loadKnownSessions(data) {
        for (const [sessionId, session] of Object.entries(data)) {
            this.knownSessions.set(sessionId, session);
        }
    }
    // --- Heartbeat ---
    cleanupDeadConnections(notifyFn) {
        const removedAgents = [];
        const removedClients = [];
        for (const [agentId, agent] of this.agents) {
            if (!agent.isAlive) {
                console.log(`[Heartbeat] Agent ${agent.name} timed out`);
                agent.ws.terminate();
                this.sessionToAgent.delete(agent.sessionId);
                this.agents.delete(agentId);
                removedAgents.push(agentId);
                // Update lastSeen in knownSessions
                const known = this.knownSessions.get(agent.sessionId);
                if (known)
                    known.lastSeen = Date.now();
                // Use reverse index for O(1) client lookup
                for (const client of this.getClientsForSession(agent.sessionId)) {
                    notifyFn(client, { type: 'agent_disconnected' });
                }
                continue;
            }
            agent.isAlive = false;
            try {
                agent.ws.ping();
            }
            catch { /* swallow */ }
        }
        for (const [clientId, client] of this.webClients) {
            if (!client.isAlive) {
                client.ws.terminate();
                this.removeClient(clientId);
                removedClients.push(clientId);
                continue;
            }
            client.isAlive = false;
            try {
                client.ws.ping();
            }
            catch { /* swallow */ }
        }
        // Prune expired session owners (no agent + no clients + past TTL)
        const now = Date.now();
        let pruned = false;
        for (const [sessionId, owner] of this.sessionOwners) {
            if (now - owner.updatedAt > SessionManager.SESSION_OWNER_TTL
                && !this.sessionToAgent.has(sessionId)
                && !this.getClientsForSession(sessionId).length) {
                this.sessionOwners.delete(sessionId);
                pruned = true;
            }
        }
        // Prune expired knownSessions (no agent + past 7-day TTL)
        for (const [sessionId, session] of this.knownSessions) {
            if (now - session.lastSeen > SessionManager.KNOWN_SESSION_TTL
                && !this.sessionToAgent.has(sessionId)) {
                this.knownSessions.delete(sessionId);
                pruned = true;
            }
        }
        if (pruned || removedAgents.length > 0) {
            this.onMutation?.();
        }
        return { removedAgents, removedClients };
    }
}
// Singleton instance for backward compatibility
export const sessions = new SessionManager();
//# sourceMappingURL=session-manager.js.map