"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.XiaoYiRuntime = void 0;
exports.getXiaoYiRuntime = getXiaoYiRuntime;
exports.setXiaoYiRuntime = setXiaoYiRuntime;
const websocket_1 = require("./websocket");
/**
 * Default timeout configuration
 */
const DEFAULT_TIMEOUT_CONFIG = {
    enabled: true,
    duration: 60000, // 60 seconds
    message: "任务正在处理中，请稍后",
};
/**
 * Runtime state for XiaoYi channel
 * Manages single WebSocket connection (single account mode)
 */
class XiaoYiRuntime {
    constructor() {
        this.connection = null;
        this.pluginRuntime = null; // Store PluginRuntime from OpenClaw
        this.config = null;
        this.sessionToTaskIdMap = new Map(); // Map sessionId to taskId
        // Timeout management
        this.sessionTimeoutMap = new Map();
        this.sessionTimeoutSent = new Set();
        this.timeoutConfig = DEFAULT_TIMEOUT_CONFIG;
        // AbortController management for canceling agent runs
        this.sessionAbortControllerMap = new Map();
        // Track if a session has an active agent run (for concurrent request detection)
        this.sessionActiveRunMap = new Map();
        // Track session start time for timeout detection
        this.sessionStartTimeMap = new Map();
        // 1-hour task timeout mechanism
        this.sessionTaskTimeoutMap = new Map();
        this.sessionPushPendingMap = new Map();
        this.taskTimeoutMs = 3600000; // Default 1 hour
        this.instanceId = `runtime_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
        console.log(`XiaoYi: Created new runtime instance: ${this.instanceId}`);
    }
    getInstanceId() {
        return this.instanceId;
    }
    /**
     * Set OpenClaw PluginRuntime (from api.runtime in register())
     */
    setPluginRuntime(runtime) {
        console.log(`XiaoYi: [${this.instanceId}] Setting PluginRuntime`);
        this.pluginRuntime = runtime;
    }
    /**
     * Get OpenClaw PluginRuntime
     */
    getPluginRuntime() {
        return this.pluginRuntime;
    }
    /**
     * Start connection (single account mode)
     */
    async start(config) {
        if (this.connection) {
            console.log("XiaoYi channel already connected");
            return;
        }
        this.config = config;
        const manager = new websocket_1.XiaoYiWebSocketManager(config);
        // Setup basic event handlers (message handling is done in channel.ts)
        manager.on("error", (error) => {
            console.error("XiaoYi channel error:", error);
        });
        manager.on("disconnected", () => {
            console.log("XiaoYi channel disconnected");
        });
        manager.on("authenticated", () => {
            console.log("XiaoYi channel authenticated");
        });
        manager.on("maxReconnectAttemptsReached", (serverId) => {
            console.error(`XiaoYi channel ${serverId} max reconnect attempts reached`);
            // Check if the other server is still connected and ready
            const otherServerId = serverId === 'server1' ? 'server2' : 'server1';
            const serverStates = manager.getServerStates();
            const otherServerState = otherServerId === 'server1' ? serverStates.server1 : serverStates.server2;
            if (otherServerState?.connected && otherServerState?.ready) {
                console.warn(`[${otherServerId}] is still connected and ready, continuing in single-server mode`);
                console.warn(`System will continue running with ${otherServerId} only`);
                // Don't stop, continue with the other server
                return;
            }
            // Only stop when both servers have failed
            console.error("Both servers have reached max reconnect attempts, stopping connection");
            console.error(`Server1: ${serverStates.server1.connected ? 'connected' : 'disconnected'}, Server2: ${serverStates.server2.connected ? 'connected' : 'disconnected'}`);
            this.stop();
        });
        // Connect
        await manager.connect();
        this.connection = manager;
        console.log("XiaoYi channel started");
    }
    /**
     * Stop connection
     */
    stop() {
        if (this.connection) {
            this.connection.disconnect();
            this.connection = null;
            console.log("XiaoYi channel stopped");
        }
        // Clear session mappings
        this.sessionToTaskIdMap.clear();
        // Clear all timeouts
        this.clearAllTimeouts();
        // Clear all abort controllers
        this.clearAllAbortControllers();
        // Clear all task timeout state
        for (const sessionId of this.sessionTaskTimeoutMap.keys()) {
            this.clearTaskTimeoutState(sessionId);
        }
    }
    /**
     * Set timeout configuration
     */
    setTimeoutConfig(config) {
        this.timeoutConfig = { ...this.timeoutConfig, ...config };
        console.log(`XiaoYi: Timeout config updated:`, this.timeoutConfig);
    }
    /**
     * Get timeout configuration
     */
    getTimeoutConfig() {
        return { ...this.timeoutConfig };
    }
    /**
     * Set timeout for a session
     * @param sessionId - Session ID
     * @param callback - Function to call when timeout occurs
     * @returns The interval ID (for cancellation)
     *
     * IMPORTANT: This now uses setInterval instead of setTimeout
     * - First trigger: after 60 seconds
     * - Subsequent triggers: every 60 seconds after that
     * - Cleared when: response received, session completed, or explicitly cleared
     */
    setTimeoutForSession(sessionId, callback) {
        if (!this.timeoutConfig.enabled) {
            console.log(`[TIMEOUT] Timeout disabled, skipping for session ${sessionId}`);
            return undefined;
        }
        // Clear existing timeout AND timeout flag if any (reuse session scenario)
        const hadExistingTimeout = this.sessionTimeoutMap.has(sessionId);
        const hadSentTimeout = this.sessionTimeoutSent.has(sessionId);
        this.clearSessionTimeout(sessionId);
        // Clear the timeout sent flag to allow this session to timeout again
        if (hadSentTimeout) {
            this.sessionTimeoutSent.delete(sessionId);
            console.log(`[TIMEOUT] Previous timeout flag cleared for session ${sessionId} (session reuse)`);
        }
        // Use setInterval for periodic timeout triggers
        // First trigger after duration, then every duration after that
        const intervalId = setInterval(() => {
            console.log(`[TIMEOUT] Timeout triggered for session ${sessionId} (will trigger again in ${this.timeoutConfig.duration}ms if still active)`);
            this.sessionTimeoutSent.add(sessionId);
            callback();
        }, this.timeoutConfig.duration);
        this.sessionTimeoutMap.set(sessionId, intervalId);
        const logSuffix = hadExistingTimeout ? " (replacing existing interval)" : "";
        console.log(`[TIMEOUT] ${this.timeoutConfig.duration}ms periodic timeout started for session ${sessionId}${logSuffix}`);
        return intervalId;
    }
    /**
     * Clear timeout interval for a session
     * @param sessionId - Session ID
     */
    clearSessionTimeout(sessionId) {
        const intervalId = this.sessionTimeoutMap.get(sessionId);
        if (intervalId) {
            clearInterval(intervalId);
            this.sessionTimeoutMap.delete(sessionId);
            console.log(`[TIMEOUT] Timeout interval cleared for session ${sessionId}`);
        }
    }
    /**
     * Check if timeout has been sent for a session
     * @param sessionId - Session ID
     */
    isSessionTimeout(sessionId) {
        return this.sessionTimeoutSent.has(sessionId);
    }
    /**
     * Mark session as completed (clear timeout and timeout flag)
     * @param sessionId - Session ID
     */
    markSessionCompleted(sessionId) {
        this.clearSessionTimeout(sessionId);
        this.sessionTimeoutSent.delete(sessionId);
        console.log(`[TIMEOUT] Session ${sessionId} marked as completed`);
    }
    /**
     * Clear all timeout intervals
     */
    clearAllTimeouts() {
        for (const [sessionId, intervalId] of this.sessionTimeoutMap.entries()) {
            clearInterval(intervalId);
        }
        this.sessionTimeoutMap.clear();
        this.sessionTimeoutSent.clear();
        console.log("[TIMEOUT] All timeout intervals cleared");
    }
    /**
     * Get WebSocket manager
     */
    getConnection() {
        return this.connection;
    }
    /**
     * Check if connected
     */
    isConnected() {
        return this.connection ? this.connection.isReady() : false;
    }
    /**
     * Get configuration
     */
    getConfig() {
        return this.config;
    }
    /**
     * Set taskId for a session
     */
    setTaskIdForSession(sessionId, taskId) {
        this.sessionToTaskIdMap.set(sessionId, taskId);
    }
    /**
     * Get taskId for a session
     */
    getTaskIdForSession(sessionId) {
        return this.sessionToTaskIdMap.get(sessionId);
    }
    /**
     * Clear taskId for a session
     */
    clearTaskIdForSession(sessionId) {
        this.sessionToTaskIdMap.delete(sessionId);
    }
    /**
     * Create and register an AbortController for a session
     * @param sessionId - Session ID
     * @returns The AbortController and its signal, or null if session is busy
     */
    createAbortControllerForSession(sessionId) {
        // Check if there's an active agent run for this session
        if (this.sessionActiveRunMap.get(sessionId)) {
            console.log(`[CONCURRENT] Session ${sessionId} has an active agent run, cannot create new AbortController`);
            return null;
        }
        const controller = new AbortController();
        this.sessionAbortControllerMap.set(sessionId, controller);
        this.sessionActiveRunMap.set(sessionId, true);
        this.sessionStartTimeMap.set(sessionId, Date.now());
        console.log(`[ABORT] Created AbortController for session ${sessionId}`);
        return { controller, signal: controller.signal };
    }
    /**
     * Check if a session has an active agent run
     * If session is active but stale (超过 SESSION_STALE_TIMEOUT_MS), automatically clean up
     * @param sessionId - Session ID
     * @returns true if session is busy
     */
    isSessionActive(sessionId) {
        const isActive = this.sessionActiveRunMap.get(sessionId) || false;
        if (isActive) {
            // Check if the session has been active for too long
            const startTime = this.sessionStartTimeMap.get(sessionId);
            if (startTime) {
                const elapsed = Date.now() - startTime;
                if (elapsed > XiaoYiRuntime.SESSION_STALE_TIMEOUT_MS) {
                    // Session is stale, auto-cleanup and return false
                    console.log(`[CONCURRENT] Session ${sessionId} is stale (active for ${elapsed}ms), auto-cleaning`);
                    this.clearAbortControllerForSession(sessionId);
                    this.clearTaskIdForSession(sessionId);
                    this.clearSessionTimeout(sessionId);
                    this.sessionStartTimeMap.delete(sessionId);
                    return false;
                }
            }
        }
        return isActive;
    }
    /**
     * Abort a session's agent run
     * @param sessionId - Session ID
     * @returns true if a controller was found and aborted, false otherwise
     */
    abortSession(sessionId) {
        const controller = this.sessionAbortControllerMap.get(sessionId);
        if (controller) {
            console.log(`[ABORT] Aborting session ${sessionId}`);
            controller.abort();
            this.sessionAbortControllerMap.delete(sessionId);
            return true;
        }
        console.log(`[ABORT] No AbortController found for session ${sessionId}`);
        return false;
    }
    /**
     * Check if a session has been aborted
     * @param sessionId - Session ID
     * @returns true if the session's abort signal was triggered
     */
    isSessionAborted(sessionId) {
        const controller = this.sessionAbortControllerMap.get(sessionId);
        return controller ? controller.signal.aborted : false;
    }
    /**
     * Clear the AbortController for a session (call when agent completes successfully)
     * @param sessionId - Session ID
     */
    clearAbortControllerForSession(sessionId) {
        const controller = this.sessionAbortControllerMap.get(sessionId);
        if (controller) {
            this.sessionAbortControllerMap.delete(sessionId);
            console.log(`[ABORT] Cleared AbortController for session ${sessionId}`);
        }
        // Also clear the active run flag
        this.sessionActiveRunMap.delete(sessionId);
        // Clear the session start time
        this.sessionStartTimeMap.delete(sessionId);
        console.log(`[CONCURRENT] Session ${sessionId} marked as inactive`);
    }
    /**
     * Clear all AbortControllers
     */
    clearAllAbortControllers() {
        this.sessionAbortControllerMap.clear();
        console.log("[ABORT] All AbortControllers cleared");
    }
    // ==================== PUSH STATE MANAGEMENT HELPERS ====================
    /**
     * Generate a composite key for session+task combination
     * This ensures each task has its own push state, even within the same session
     */
    getPushStateKey(sessionId, taskId) {
        return `${sessionId}:${taskId}`;
    }
    // ==================== END PUSH STATE MANAGEMENT HELPERS ====================
    // ==================== 1-HOUR TASK TIMEOUT METHODS ====================
    /**
     * Set task timeout time (from configuration)
     */
    setTaskTimeout(timeoutMs) {
        this.taskTimeoutMs = timeoutMs;
        console.log(`[TASK TIMEOUT] Task timeout set to ${timeoutMs}ms`);
    }
    /**
     * Set a 1-hour task timeout timer for a session
     * @returns timeout ID
     */
    setTaskTimeoutForSession(sessionId, taskId, callback) {
        this.clearTaskTimeoutForSession(sessionId);
        const timeoutId = setTimeout(() => {
            console.log(`[TASK TIMEOUT] ${this.taskTimeoutMs}ms timeout triggered for session ${sessionId}, task ${taskId}`);
            callback(sessionId, taskId);
        }, this.taskTimeoutMs);
        this.sessionTaskTimeoutMap.set(sessionId, timeoutId);
        console.log(`[TASK TIMEOUT] ${this.taskTimeoutMs}ms task timeout started for session ${sessionId}`);
        return timeoutId;
    }
    /**
     * Clear the task timeout timer for a session
     */
    clearTaskTimeoutForSession(sessionId) {
        const timeoutId = this.sessionTaskTimeoutMap.get(sessionId);
        if (timeoutId) {
            clearTimeout(timeoutId);
            this.sessionTaskTimeoutMap.delete(sessionId);
            console.log(`[TASK TIMEOUT] Timeout cleared for session ${sessionId}`);
        }
    }
    /**
     * Check if session+task is waiting for push notification
     * @param sessionId - Session ID
     * @param taskId - Task ID (optional, for per-task tracking)
     */
    isSessionWaitingForPush(sessionId, taskId) {
        const key = taskId ? this.getPushStateKey(sessionId, taskId) : sessionId;
        return this.sessionPushPendingMap.get(key) === true;
    }
    /**
     * Mark session+task as waiting for push notification
     * @param sessionId - Session ID
     * @param taskId - Task ID (optional, for per-task tracking)
     */
    markSessionWaitingForPush(sessionId, taskId) {
        const key = taskId ? this.getPushStateKey(sessionId, taskId) : sessionId;
        this.sessionPushPendingMap.set(key, true);
        const taskInfo = taskId ? `, task ${taskId}` : '';
        console.log(`[PUSH] Session ${sessionId}${taskInfo} marked as waiting for push`);
    }
    /**
     * Clear the waiting push state for a session+task
     * @param sessionId - Session ID
     * @param taskId - Task ID (optional, for per-task tracking)
     */
    clearSessionWaitingForPush(sessionId, taskId) {
        const key = taskId ? this.getPushStateKey(sessionId, taskId) : sessionId;
        this.sessionPushPendingMap.delete(key);
        const taskInfo = taskId ? `, task ${taskId}` : '';
        console.log(`[PUSH] Session ${sessionId}${taskInfo} cleared from waiting for push`);
    }
    /**
     * Clear all task timeout related state for a session
     */
    clearTaskTimeoutState(sessionId) {
        this.clearTaskTimeoutForSession(sessionId);
        this.clearSessionWaitingForPush(sessionId);
        console.log(`[TASK TIMEOUT] All timeout state cleared for session ${sessionId}`);
    }
}
exports.XiaoYiRuntime = XiaoYiRuntime;
// Maximum time a session can be active before we consider it stale (5 minutes)
XiaoYiRuntime.SESSION_STALE_TIMEOUT_MS = 5 * 60 * 1000;
// Global runtime instance - use global object to survive module reloads
// CRITICAL: Use string key instead of Symbol to ensure consistency across module reloads
const GLOBAL_KEY = '__xiaoyi_runtime_instance__';
function getXiaoYiRuntime() {
    const g = global;
    if (!g[GLOBAL_KEY]) {
        console.log("XiaoYi: Creating NEW runtime instance (global storage)");
        g[GLOBAL_KEY] = new XiaoYiRuntime();
    }
    else {
        console.log(`XiaoYi: Reusing EXISTING runtime instance: ${g[GLOBAL_KEY].getInstanceId()}`);
    }
    return g[GLOBAL_KEY];
}
function setXiaoYiRuntime(runtime) {
    getXiaoYiRuntime().setPluginRuntime(runtime);
}
