"use strict";
/**
 * Auto Yes Manager - Server-side in-memory state management
 *
 * Manages auto-yes mode per worktree using an in-memory Map.
 * State is automatically cleared on server restart.
 *
 * Issue #138: Added server-side polling functionality to handle
 * auto-yes responses when browser tabs are in background.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.MAX_CONCURRENT_POLLERS = exports.MAX_CONSECUTIVE_ERRORS = exports.MAX_BACKOFF_MS = exports.POLLING_INTERVAL_MS = void 0;
exports.isValidWorktreeId = isValidWorktreeId;
exports.calculateBackoffInterval = calculateBackoffInterval;
exports.isAutoYesExpired = isAutoYesExpired;
exports.getAutoYesState = getAutoYesState;
exports.setAutoYesEnabled = setAutoYesEnabled;
exports.clearAllAutoYesStates = clearAllAutoYesStates;
exports.getActivePollerCount = getActivePollerCount;
exports.clearAllPollerStates = clearAllPollerStates;
exports.getLastServerResponseTimestamp = getLastServerResponseTimestamp;
exports.startAutoYesPolling = startAutoYesPolling;
exports.stopAutoYesPolling = stopAutoYesPolling;
exports.stopAllAutoYesPolling = stopAllAutoYesPolling;
const cli_session_1 = require("./cli-session");
const prompt_detector_1 = require("./prompt-detector");
const auto_yes_resolver_1 = require("./auto-yes-resolver");
const tmux_1 = require("./tmux");
const manager_1 = require("./cli-tools/manager");
const cli_patterns_1 = require("./cli-patterns");
// =============================================================================
// Constants (Issue #138)
// =============================================================================
/** Polling interval in milliseconds */
exports.POLLING_INTERVAL_MS = 2000;
/** Maximum backoff interval in milliseconds (60 seconds) */
exports.MAX_BACKOFF_MS = 60000;
/** Number of consecutive errors before applying backoff */
exports.MAX_CONSECUTIVE_ERRORS = 5;
/** Maximum concurrent pollers (DoS protection) */
exports.MAX_CONCURRENT_POLLERS = 50;
/** Timeout duration: 1 hour in milliseconds */
const AUTO_YES_TIMEOUT_MS = 3600000;
/** Worktree ID validation pattern (security: prevent command injection) */
const WORKTREE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
// =============================================================================
// In-memory State
// =============================================================================
/** In-memory storage for auto-yes states */
const autoYesStates = new Map();
/** In-memory storage for poller states (Issue #138) */
const autoYesPollerStates = new Map();
// =============================================================================
// Utility Functions
// =============================================================================
/**
 * Validate worktree ID format (security measure)
 * Only allows alphanumeric characters, hyphens, and underscores.
 */
function isValidWorktreeId(worktreeId) {
    if (!worktreeId || worktreeId.length === 0)
        return false;
    return WORKTREE_ID_PATTERN.test(worktreeId);
}
/**
 * Calculate backoff interval based on consecutive errors
 */
function calculateBackoffInterval(consecutiveErrors) {
    if (consecutiveErrors < exports.MAX_CONSECUTIVE_ERRORS) {
        return exports.POLLING_INTERVAL_MS;
    }
    // Exponential backoff: 2^(errors - 4) * 2000
    // 5 errors: 2^1 * 2000 = 4000
    // 6 errors: 2^2 * 2000 = 8000
    // etc.
    const backoffMultiplier = Math.pow(2, consecutiveErrors - exports.MAX_CONSECUTIVE_ERRORS + 1);
    const backoffMs = exports.POLLING_INTERVAL_MS * backoffMultiplier;
    return Math.min(backoffMs, exports.MAX_BACKOFF_MS);
}
// =============================================================================
// Auto-Yes State Management (Existing)
// =============================================================================
/**
 * Check if an auto-yes state has expired
 */
function isAutoYesExpired(state) {
    return Date.now() > state.expiresAt;
}
/**
 * Get the auto-yes state for a worktree
 * Returns null if no state exists or if expired (auto-disables on expiry)
 */
function getAutoYesState(worktreeId) {
    const state = autoYesStates.get(worktreeId);
    if (!state)
        return null;
    // Auto-disable if expired
    if (isAutoYesExpired(state)) {
        const disabledState = {
            ...state,
            enabled: false,
        };
        autoYesStates.set(worktreeId, disabledState);
        return disabledState;
    }
    return state;
}
/**
 * Set the auto-yes enabled state for a worktree
 */
function setAutoYesEnabled(worktreeId, enabled) {
    if (enabled) {
        const now = Date.now();
        const state = {
            enabled: true,
            enabledAt: now,
            expiresAt: now + AUTO_YES_TIMEOUT_MS,
        };
        autoYesStates.set(worktreeId, state);
        return state;
    }
    else {
        const existing = autoYesStates.get(worktreeId);
        const state = {
            enabled: false,
            enabledAt: existing?.enabledAt ?? 0,
            expiresAt: existing?.expiresAt ?? 0,
        };
        autoYesStates.set(worktreeId, state);
        return state;
    }
}
/**
 * Clear all auto-yes states (for testing)
 */
function clearAllAutoYesStates() {
    autoYesStates.clear();
}
// =============================================================================
// Server-side Polling (Issue #138)
// =============================================================================
/**
 * Get the number of active pollers
 */
function getActivePollerCount() {
    return autoYesPollerStates.size;
}
/**
 * Clear all poller states (for testing)
 */
function clearAllPollerStates() {
    stopAllAutoYesPolling();
    autoYesPollerStates.clear();
}
/**
 * Get the last server response timestamp for a worktree
 * Used by clients to prevent duplicate responses
 */
function getLastServerResponseTimestamp(worktreeId) {
    const pollerState = autoYesPollerStates.get(worktreeId);
    return pollerState?.lastServerResponseTimestamp ?? null;
}
/**
 * Update the last server response timestamp
 */
function updateLastServerResponseTimestamp(worktreeId, timestamp) {
    const pollerState = autoYesPollerStates.get(worktreeId);
    if (pollerState) {
        pollerState.lastServerResponseTimestamp = timestamp;
    }
}
/**
 * Reset error count for a poller
 */
function resetErrorCount(worktreeId) {
    const pollerState = autoYesPollerStates.get(worktreeId);
    if (pollerState) {
        pollerState.consecutiveErrors = 0;
        pollerState.currentInterval = exports.POLLING_INTERVAL_MS;
    }
}
/**
 * Increment error count and apply backoff if needed
 */
function incrementErrorCount(worktreeId) {
    const pollerState = autoYesPollerStates.get(worktreeId);
    if (pollerState) {
        pollerState.consecutiveErrors++;
        pollerState.currentInterval = calculateBackoffInterval(pollerState.consecutiveErrors);
    }
}
/**
 * Internal polling function (setTimeout recursive)
 */
async function pollAutoYes(worktreeId, cliToolId) {
    // Check if poller was stopped
    const pollerState = autoYesPollerStates.get(worktreeId);
    if (!pollerState)
        return;
    // Check if auto-yes is still enabled
    const autoYesState = getAutoYesState(worktreeId);
    if (!autoYesState?.enabled || isAutoYesExpired(autoYesState)) {
        stopAutoYesPolling(worktreeId);
        return;
    }
    try {
        // 1. Capture tmux output
        const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, 5000);
        // 2. Strip ANSI codes and detect prompt
        const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
        const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanOutput);
        if (!promptDetection.isPrompt || !promptDetection.promptData) {
            // No prompt detected, schedule next poll
            scheduleNextPoll(worktreeId, cliToolId);
            return;
        }
        // 3. Resolve auto answer
        const answer = (0, auto_yes_resolver_1.resolveAutoAnswer)(promptDetection.promptData);
        if (answer === null) {
            // Cannot auto-answer this prompt
            scheduleNextPoll(worktreeId, cliToolId);
            return;
        }
        // 4. Send answer to tmux
        const manager = manager_1.CLIToolManager.getInstance();
        const cliTool = manager.getTool(cliToolId);
        const sessionName = cliTool.getSessionName(worktreeId);
        // Send answer followed by Enter
        await (0, tmux_1.sendKeys)(sessionName, answer, false);
        await new Promise(resolve => setTimeout(resolve, 100));
        await (0, tmux_1.sendKeys)(sessionName, '', true);
        // 5. Update timestamp
        updateLastServerResponseTimestamp(worktreeId, Date.now());
        // 6. Reset error count on success
        resetErrorCount(worktreeId);
        // Log success (without sensitive content)
        console.info(`[Auto-Yes Poller] Sent response for worktree: ${worktreeId}`);
    }
    catch (error) {
        // Increment error count on failure
        incrementErrorCount(worktreeId);
        // Log error (without sensitive details)
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        console.warn(`[Auto-Yes Poller] Error for worktree ${worktreeId}: ${errorMessage}`);
    }
    // Schedule next poll
    scheduleNextPoll(worktreeId, cliToolId);
}
/**
 * Schedule the next polling iteration
 */
function scheduleNextPoll(worktreeId, cliToolId) {
    const pollerState = autoYesPollerStates.get(worktreeId);
    if (!pollerState)
        return;
    pollerState.timerId = setTimeout(() => {
        pollAutoYes(worktreeId, cliToolId);
    }, pollerState.currentInterval);
}
/**
 * Start server-side auto-yes polling for a worktree
 */
function startAutoYesPolling(worktreeId, cliToolId) {
    // Validate worktree ID (security)
    if (!isValidWorktreeId(worktreeId)) {
        return { started: false, reason: 'invalid worktree ID' };
    }
    // Check if auto-yes is enabled
    const autoYesState = getAutoYesState(worktreeId);
    if (!autoYesState?.enabled) {
        return { started: false, reason: 'auto-yes not enabled' };
    }
    // Check concurrent poller limit (DoS protection)
    // If this worktree already has a poller, don't count it toward the limit
    const existingPoller = autoYesPollerStates.has(worktreeId);
    if (!existingPoller && autoYesPollerStates.size >= exports.MAX_CONCURRENT_POLLERS) {
        return { started: false, reason: 'max concurrent pollers reached' };
    }
    // Stop existing poller if any
    if (existingPoller) {
        stopAutoYesPolling(worktreeId);
    }
    // Create new poller state
    const pollerState = {
        timerId: null,
        cliToolId,
        consecutiveErrors: 0,
        currentInterval: exports.POLLING_INTERVAL_MS,
        lastServerResponseTimestamp: null,
    };
    autoYesPollerStates.set(worktreeId, pollerState);
    // Start polling immediately
    pollerState.timerId = setTimeout(() => {
        pollAutoYes(worktreeId, cliToolId);
    }, exports.POLLING_INTERVAL_MS);
    console.info(`[Auto-Yes Poller] Started for worktree: ${worktreeId}, cliTool: ${cliToolId}`);
    return { started: true };
}
/**
 * Stop server-side auto-yes polling for a worktree
 */
function stopAutoYesPolling(worktreeId) {
    const pollerState = autoYesPollerStates.get(worktreeId);
    if (!pollerState)
        return;
    // Clear timer
    if (pollerState.timerId) {
        clearTimeout(pollerState.timerId);
    }
    // Remove state
    autoYesPollerStates.delete(worktreeId);
    console.info(`[Auto-Yes Poller] Stopped for worktree: ${worktreeId}`);
}
/**
 * Stop all server-side auto-yes polling (graceful shutdown)
 */
function stopAllAutoYesPolling() {
    for (const [worktreeId, pollerState] of autoYesPollerStates.entries()) {
        if (pollerState.timerId) {
            clearTimeout(pollerState.timerId);
        }
        console.info(`[Auto-Yes Poller] Stopped for worktree: ${worktreeId} (shutdown)`);
    }
    autoYesPollerStates.clear();
}
