"use strict";
/**
 * Auto-Yes Poller - Server-side polling for auto-yes prompt responses.
 *
 * Extracted from auto-yes-manager.ts (Issue #479) to separate polling logic
 * from state management.
 *
 * Issue #525: Composite key migration (worktreeId:cliToolId) for per-agent auto-yes.
 *
 * Dependencies: auto-yes-state.ts (one-way dependency).
 * auto-yes-poller.ts -> auto-yes-state.ts
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAutoYesPollerWorktreeIds = void 0;
exports.getActivePollerCount = getActivePollerCount;
exports.clearAllPollerStates = clearAllPollerStates;
exports.getLastServerResponseTimestamp = getLastServerResponseTimestamp;
exports.isPollerActive = isPollerActive;
exports.validatePollingContext = validatePollingContext;
exports.captureAndCleanOutput = captureAndCleanOutput;
exports.processStopConditionDelta = processStopConditionDelta;
exports.detectAndRespondToPrompt = detectAndRespondToPrompt;
exports.startAutoYesPolling = startAutoYesPolling;
exports.stopAutoYesPolling = stopAutoYesPolling;
exports.stopAllAutoYesPolling = stopAllAutoYesPolling;
exports.getAutoYesPollerCompositeKeys = getAutoYesPollerCompositeKeys;
exports.stopAutoYesPollingByWorktree = stopAutoYesPollingByWorktree;
exports.isAnyPollerActiveForWorktree = isAnyPollerActiveForWorktree;
const cli_session_1 = require("./session/cli-session");
const prompt_detector_1 = require("./detection/prompt-detector");
const auto_yes_resolver_1 = require("./polling/auto-yes-resolver");
const prompt_answer_sender_1 = require("./prompt-answer-sender");
const manager_1 = require("./cli-tools/manager");
const cli_patterns_1 = require("./detection/cli-patterns");
const prompt_key_1 = require("./detection/prompt-key");
const errors_1 = require("./errors");
const tmux_capture_cache_1 = require("./tmux/tmux-capture-cache");
const auto_yes_config_1 = require("../config/auto-yes-config");
const logger_1 = require("../lib/logger");
const logger = (0, logger_1.createLogger)('auto-yes-poller');
const path_validator_1 = require("./security/path-validator");
const auto_yes_state_1 = require("./auto-yes-state");
/** In-memory storage for poller states (globalThis for hot reload persistence) */
const autoYesPollerStates = globalThis.__autoYesPollerStates ??
    (globalThis.__autoYesPollerStates = new Map());
// =============================================================================
// Poller State Accessors (compositeKey-based)
// =============================================================================
/**
 * Get poller state by composite key.
 *
 * @param compositeKey - Composite key (worktreeId:cliToolId)
 * @returns Poller state or undefined
 */
function getPollerState(compositeKey) {
    return autoYesPollerStates.get(compositeKey);
}
/**
 * Get the number of active pollers.
 */
function getActivePollerCount() {
    return autoYesPollerStates.size;
}
/**
 * Clear all poller states.
 * Stops all active pollers before clearing state.
 * @internal Exported for testing purposes only.
 */
function clearAllPollerStates() {
    stopAllAutoYesPolling();
    autoYesPollerStates.clear();
}
/**
 * Get the last server response timestamp for a composite key.
 *
 * Issue #525: Changed from (worktreeId) to (compositeKey).
 *
 * @param compositeKey - Composite key (worktreeId:cliToolId)
 * @returns Timestamp (Date.now()) of the last server response, or null if none
 */
function getLastServerResponseTimestamp(compositeKey) {
    const pollerState = getPollerState(compositeKey);
    return pollerState?.lastServerResponseTimestamp ?? null;
}
/**
 * Check if a server-side auto-yes poller is active for a composite key.
 *
 * Issue #525: Changed from (worktreeId) to (compositeKey).
 *
 * @param compositeKey - Composite key (worktreeId:cliToolId)
 * @returns true if a poller is actively running
 */
function isPollerActive(compositeKey) {
    return autoYesPollerStates.has(compositeKey);
}
/**
 * Update the last server response timestamp.
 *
 * @param compositeKey - Composite key
 * @param timestamp - Timestamp value (Date.now())
 */
function updateLastServerResponseTimestamp(compositeKey, timestamp) {
    const pollerState = getPollerState(compositeKey);
    if (pollerState) {
        pollerState.lastServerResponseTimestamp = timestamp;
    }
}
/**
 * Reset error count for a poller and restore the default polling interval.
 *
 * @param compositeKey - Composite key
 */
function resetErrorCount(compositeKey) {
    const pollerState = getPollerState(compositeKey);
    if (pollerState) {
        pollerState.consecutiveErrors = 0;
        pollerState.currentInterval = auto_yes_state_1.POLLING_INTERVAL_MS;
    }
}
/**
 * Increment error count and apply backoff if the threshold is exceeded.
 * [IA-MF-001] compositeKey-based: extracts worktreeId/cliToolId for disableAutoYes.
 *
 * @param compositeKey - Composite key
 */
function incrementErrorCount(compositeKey) {
    const pollerState = getPollerState(compositeKey);
    if (pollerState) {
        pollerState.consecutiveErrors++;
        pollerState.currentInterval = (0, auto_yes_state_1.calculateBackoffInterval)(pollerState.consecutiveErrors);
        // Issue #499 Item 5: Auto-stop after consecutive error threshold.
        if (pollerState.consecutiveErrors >= auto_yes_config_1.AUTO_STOP_ERROR_THRESHOLD) {
            const worktreeId = (0, auto_yes_state_1.extractWorktreeId)(compositeKey);
            const cliToolId = (0, auto_yes_state_1.extractCliToolId)(compositeKey);
            if (cliToolId) {
                (0, auto_yes_state_1.disableAutoYes)(worktreeId, cliToolId, 'consecutive_errors');
            }
            stopAutoYesPolling(compositeKey);
        }
    }
}
/**
 * Check if the given prompt has already been answered recently.
 *
 * @param pollerState - Current poller state
 * @param promptKey - Composite key of the current prompt
 * @returns true if the prompt key matches and is within the retry expiry window
 */
function isDuplicatePrompt(pollerState, promptKey) {
    if (pollerState.lastAnsweredPromptKey !== promptKey)
        return false;
    if (pollerState.lastAnsweredAt === null)
        return false;
    return (Date.now() - pollerState.lastAnsweredAt) < auto_yes_state_1.DUPLICATE_RETRY_EXPIRY_MS;
}
// =============================================================================
// Extracted Functions for pollAutoYes (Issue #323: SRP decomposition)
// =============================================================================
/**
 * Validate that polling context is still valid.
 * Checks pollerState existence and auto-yes enabled state.
 *
 * Issue #525: Changed from (worktreeId, pollerState) to (compositeKey, pollerState).
 *
 * @internal Exported for testing purposes only.
 * @param compositeKey - Composite key (worktreeId:cliToolId)
 * @param pollerState - Current poller state (or undefined if not found)
 * @returns 'valid' | 'stopped' | 'expired'
 */
function validatePollingContext(compositeKey, pollerState) {
    if (!pollerState)
        return 'stopped';
    const worktreeId = (0, auto_yes_state_1.extractWorktreeId)(compositeKey);
    const cliToolId = (0, auto_yes_state_1.extractCliToolId)(compositeKey);
    if (!cliToolId) {
        stopAutoYesPolling(compositeKey);
        return 'expired';
    }
    const autoYesState = (0, auto_yes_state_1.getAutoYesState)(worktreeId, cliToolId);
    if (!autoYesState?.enabled) {
        stopAutoYesPolling(compositeKey);
        return 'expired';
    }
    return 'valid';
}
/**
 * Capture tmux session output and strip ANSI escape codes.
 *
 * @internal Exported for testing purposes only.
 * @param worktreeId - Worktree identifier
 * @param cliToolId - CLI tool type being polled
 * @returns Cleaned output string (ANSI stripped)
 */
async function captureAndCleanOutput(worktreeId, cliToolId, captureLines) {
    const lines = captureLines ?? auto_yes_config_1.FULL_CAPTURE_LINES;
    const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, lines);
    return (0, cli_patterns_1.stripBoxDrawing)((0, cli_patterns_1.stripAnsi)(output));
}
/**
 * Process stop condition check using delta-based approach.
 *
 * Issue #525: Changed from (worktreeId, ...) to (compositeKey, ...).
 *
 * @internal Exported for testing purposes only.
 * @param compositeKey - Composite key (worktreeId:cliToolId)
 * @param pollerState - Current poller state (mutated: stopCheckBaselineLength updated)
 * @param cleanOutput - ANSI-stripped terminal output
 * @returns true if stop condition matched and auto-yes was disabled
 */
function processStopConditionDelta(compositeKey, pollerState, cleanOutput) {
    if (pollerState.stopCheckBaselineLength < 0) {
        pollerState.stopCheckBaselineLength = cleanOutput.length;
        return false;
    }
    const baseline = pollerState.stopCheckBaselineLength;
    if (cleanOutput.length > baseline) {
        const newContent = cleanOutput.substring(baseline);
        pollerState.stopCheckBaselineLength = cleanOutput.length;
        return (0, auto_yes_state_1.checkStopCondition)(compositeKey, newContent, stopAutoYesPolling);
    }
    else if (cleanOutput.length < baseline) {
        pollerState.stopCheckBaselineLength = cleanOutput.length;
    }
    return false;
}
/**
 * Detect prompt in terminal output, resolve auto-answer, and send response.
 *
 * @internal Exported for testing purposes only.
 * @param worktreeId - Worktree identifier
 * @param pollerState - Current poller state (mutated: lastAnsweredPromptKey updated)
 * @param cliToolId - CLI tool type
 * @param cleanOutput - ANSI-stripped terminal output
 * @returns 'responded' | 'no_prompt' | 'duplicate' | 'no_answer' | 'error'
 */
async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, cleanOutput, precomputedLines) {
    const compositeKey = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId);
    try {
        // 1. Detect prompt
        const promptOptions = (0, cli_patterns_1.buildDetectPromptOptions)(cliToolId);
        const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanOutput, {
            ...promptOptions,
            ...(precomputedLines && { precomputedLines }),
        });
        if (!promptDetection.isPrompt || !promptDetection.promptData) {
            pollerState.lastAnsweredPromptKey = null;
            pollerState.lastAnsweredAt = null;
            return 'no_prompt';
        }
        // 2. Check for duplicate prompt (Issue #306)
        const promptKey = (0, prompt_key_1.generatePromptKey)(promptDetection.promptData);
        if (isDuplicatePrompt(pollerState, promptKey)) {
            return 'duplicate';
        }
        // 3. Resolve auto answer
        const answer = (0, auto_yes_resolver_1.resolveAutoAnswer)(promptDetection.promptData);
        if (answer === null) {
            return 'no_answer';
        }
        // 4. Send answer to tmux
        const manager = manager_1.CLIToolManager.getInstance();
        const cliTool = manager.getTool(cliToolId);
        const sessionName = cliTool.getSessionName(worktreeId);
        try {
            await (0, prompt_answer_sender_1.sendPromptAnswer)({
                sessionName,
                answer,
                cliToolId,
                promptData: promptDetection.promptData,
            });
        }
        finally {
            (0, tmux_capture_cache_1.invalidateCache)(sessionName);
        }
        // 5. Update timestamp and reset error count
        updateLastServerResponseTimestamp(compositeKey, Date.now());
        resetErrorCount(compositeKey);
        // 6. Record answered prompt key and timestamp
        pollerState.lastAnsweredPromptKey = promptKey;
        pollerState.lastAnsweredAt = Date.now();
        logger.info('poller:response-sent', { worktreeId, cliToolId });
        return 'responded';
    }
    catch {
        incrementErrorCount(compositeKey);
        logger.warn('poller:detect-respond-error', { worktreeId, cliToolId });
        return 'error';
    }
}
/**
 * Internal polling function that recursively schedules itself via setTimeout.
 *
 * @param worktreeId - Worktree identifier
 * @param cliToolId - CLI tool type being polled
 */
async function pollAutoYes(worktreeId, cliToolId) {
    const compositeKey = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId);
    // 1. Validate context
    const pollerState = getPollerState(compositeKey);
    const contextResult = validatePollingContext(compositeKey, pollerState);
    if (contextResult !== 'valid') {
        return;
    }
    try {
        // 2. Capture and clean output
        const autoYesState = (0, auto_yes_state_1.getAutoYesState)(worktreeId, cliToolId);
        const captureLines = autoYesState?.stopPattern
            ? auto_yes_config_1.FULL_CAPTURE_LINES
            : auto_yes_config_1.REDUCED_CAPTURE_LINES;
        const cleanOutput = await captureAndCleanOutput(worktreeId, cliToolId, captureLines);
        const lines = cleanOutput.split('\n');
        // 3. Stop condition delta check (Issue #314)
        if (processStopConditionDelta(compositeKey, pollerState, cleanOutput)) {
            return;
        }
        // 4. Detect and respond to prompt
        const result = await detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, cleanOutput, lines);
        if (result === 'responded') {
            scheduleNextPoll(worktreeId, cliToolId, auto_yes_state_1.COOLDOWN_INTERVAL_MS);
            return;
        }
        // 5. Thinking check
        if (result === 'no_prompt') {
            const recentLines = lines.slice(-auto_yes_state_1.THINKING_CHECK_LINE_COUNT).join('\n');
            if ((0, cli_patterns_1.detectThinking)(cliToolId, recentLines)) {
                scheduleNextPoll(worktreeId, cliToolId, auto_yes_config_1.THINKING_POLLING_INTERVAL_MS);
                return;
            }
        }
    }
    catch (error) {
        incrementErrorCount(compositeKey);
        logger.warn('poller:poll-error', { worktreeId, cliToolId, error: (0, errors_1.getErrorMessage)(error) });
    }
    scheduleNextPoll(worktreeId, cliToolId);
}
/**
 * Schedule the next polling iteration
 */
function scheduleNextPoll(worktreeId, cliToolId, overrideInterval) {
    const compositeKey = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId);
    const pollerState = getPollerState(compositeKey);
    if (!pollerState)
        return;
    const interval = Math.max(overrideInterval ?? pollerState.currentInterval, auto_yes_state_1.POLLING_INTERVAL_MS);
    pollerState.timerId = setTimeout(() => {
        pollAutoYes(worktreeId, cliToolId);
    }, interval);
}
// =============================================================================
// Public Poller API
// =============================================================================
/**
 * Start server-side auto-yes polling for a worktree/agent.
 *
 * @param worktreeId - Worktree identifier (must match WORKTREE_ID_PATTERN)
 * @param cliToolId - CLI tool type to poll for
 * @returns Result indicating whether the poller was started
 */
function startAutoYesPolling(worktreeId, cliToolId) {
    // Validate worktree ID (security)
    if (!(0, path_validator_1.isValidWorktreeId)(worktreeId)) {
        return { started: false, reason: 'invalid worktree ID' };
    }
    // Check if auto-yes is enabled
    const autoYesState = (0, auto_yes_state_1.getAutoYesState)(worktreeId, cliToolId);
    if (!autoYesState?.enabled) {
        return { started: false, reason: 'auto-yes not enabled' };
    }
    const compositeKey = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId);
    // Check concurrent poller limit (DoS protection)
    const existingPollerState = getPollerState(compositeKey);
    if (!existingPollerState && autoYesPollerStates.size >= auto_yes_state_1.MAX_CONCURRENT_POLLERS) {
        return { started: false, reason: 'max concurrent pollers reached' };
    }
    // Issue #501: Idempotency check
    if (existingPollerState && existingPollerState.cliToolId === cliToolId) {
        return { started: true, reason: 'already_running' };
    }
    // Stop existing poller if cliToolId changed
    if (existingPollerState) {
        stopAutoYesPolling(compositeKey);
    }
    // Create new poller state
    const pollerState = {
        timerId: null,
        cliToolId,
        consecutiveErrors: 0,
        currentInterval: auto_yes_state_1.POLLING_INTERVAL_MS,
        lastServerResponseTimestamp: null,
        lastAnsweredPromptKey: null,
        lastAnsweredAt: null,
        stopCheckBaselineLength: -1,
    };
    autoYesPollerStates.set(compositeKey, pollerState);
    // Start polling immediately
    pollerState.timerId = setTimeout(() => {
        pollAutoYes(worktreeId, cliToolId);
    }, auto_yes_state_1.POLLING_INTERVAL_MS);
    logger.info('poller:started', { worktreeId, cliToolId });
    return { started: true };
}
/**
 * Stop server-side auto-yes polling by composite key.
 *
 * Issue #525: Changed from (worktreeId) to (compositeKey).
 *
 * @param compositeKey - Composite key (worktreeId:cliToolId)
 */
function stopAutoYesPolling(compositeKey) {
    const pollerState = getPollerState(compositeKey);
    if (!pollerState)
        return;
    if (pollerState.timerId) {
        clearTimeout(pollerState.timerId);
    }
    autoYesPollerStates.delete(compositeKey);
    logger.info('poller:stopped', { compositeKey });
}
/**
 * Stop all server-side auto-yes polling (graceful shutdown).
 */
function stopAllAutoYesPolling() {
    for (const [key, pollerState] of autoYesPollerStates.entries()) {
        if (pollerState.timerId) {
            clearTimeout(pollerState.timerId);
        }
        logger.info('poller:stopped', { compositeKey: key, reason: 'shutdown' });
    }
    autoYesPollerStates.clear();
}
/**
 * Get all composite keys that have active auto-yes poller entries.
 *
 * Issue #525: Returns composite keys (worktreeId:cliToolId).
 *
 * @returns Array of composite keys present in the autoYesPollerStates Map
 */
function getAutoYesPollerCompositeKeys() {
    return Array.from(autoYesPollerStates.keys());
}
/**
 * @deprecated Use getAutoYesPollerCompositeKeys() instead. Renamed for clarity (Issue #525).
 */
exports.getAutoYesPollerWorktreeIds = getAutoYesPollerCompositeKeys;
// =============================================================================
// byWorktree Helpers (Issue #525)
// =============================================================================
/**
 * Stop all auto-yes polling for a given worktreeId (all agents).
 * [SF-001] Uses shared filterCompositeKeysByWorktree (DRY).
 *
 * @param worktreeId - Worktree identifier
 */
function stopAutoYesPollingByWorktree(worktreeId) {
    const pollerKeys = (0, auto_yes_state_1.filterCompositeKeysByWorktree)(Array.from(autoYesPollerStates.keys()), worktreeId);
    pollerKeys.forEach(key => stopAutoYesPolling(key));
}
/**
 * Check if any auto-yes poller is active for a given worktreeId.
 * Uses shared filterCompositeKeysByWorktree (DRY).
 *
 * @param worktreeId - Worktree identifier
 * @returns true if any poller is active for this worktree
 */
function isAnyPollerActiveForWorktree(worktreeId) {
    return (0, auto_yes_state_1.filterCompositeKeysByWorktree)(Array.from(autoYesPollerStates.keys()), worktreeId).length > 0;
}
