"use strict";
/**
 * Claude CLI session management
 * Manages Claude CLI sessions within tmux for each worktree
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.CLAUDE_PROMPT_POLL_INTERVAL = exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_POST_PROMPT_DELAY = exports.CLAUDE_INIT_POLL_INTERVAL = exports.CLAUDE_INIT_TIMEOUT = void 0;
exports.getSessionName = getSessionName;
exports.isClaudeInstalled = isClaudeInstalled;
exports.isClaudeRunning = isClaudeRunning;
exports.getClaudeSessionState = getClaudeSessionState;
exports.waitForPrompt = waitForPrompt;
exports.startClaudeSession = startClaudeSession;
exports.sendMessageToClaude = sendMessageToClaude;
exports.captureClaudeOutput = captureClaudeOutput;
exports.stopClaudeSession = stopClaudeSession;
exports.restartClaudeSession = restartClaudeSession;
const tmux_1 = require("./tmux");
const cli_patterns_1 = require("./cli-patterns");
const pasted_text_helper_1 = require("./pasted-text-helper");
const child_process_1 = require("child_process");
const util_1 = require("util");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
// ----- Helper Functions -----
/**
 * Extract error message from unknown error type
 * Provides consistent error message extraction across the module (DRY)
 *
 * @param error - Unknown error object
 * @returns Error message string
 */
function getErrorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
// ----- Timeout and Polling Constants (OCP-001) -----
// These constants are exported to allow configuration and testing.
// Changing these values affects Claude CLI session startup behavior.
/**
 * Claude CLI initialization max wait time (milliseconds)
 *
 * This timeout allows sufficient time for Claude CLI to:
 * - Load and initialize its internal state
 * - Authenticate with Anthropic servers (if needed)
 * - Display the interactive prompt
 *
 * This timeout also covers trust dialog auto-response time (typically <1s).
 * When reducing this value, consider dialog response overhead.
 *
 * 15 seconds provides headroom for slower networks or cold starts.
 */
exports.CLAUDE_INIT_TIMEOUT = 15000;
/**
 * Initialization polling interval (milliseconds)
 *
 * How frequently we check if Claude CLI has finished initializing.
 * 300ms balances responsiveness with avoiding excessive polling overhead.
 */
exports.CLAUDE_INIT_POLL_INTERVAL = 300;
/**
 * Stability delay after prompt detection (milliseconds)
 *
 * This delay is necessary because Claude CLI renders its UI progressively:
 * 1. The prompt character (> or U+276F) appears first
 * 2. Additional UI elements (tips, suggestions) may render afterward
 * 3. Sending input too quickly can interrupt this rendering process
 *
 * The 500ms value was empirically determined to provide sufficient buffer
 * for Claude CLI to complete its initialization rendering while maintaining
 * responsive user experience. (DOC-001)
 *
 * @see Issue #152 - First message not being sent after session start
 */
exports.CLAUDE_POST_PROMPT_DELAY = 500;
/**
 * Prompt wait timeout before message send (milliseconds)
 *
 * When sending a message, we first verify Claude is at a prompt state.
 * This timeout limits how long we wait for Claude to return to prompt
 * if it's still processing a previous request.
 */
exports.CLAUDE_PROMPT_WAIT_TIMEOUT = 5000;
/**
 * Prompt wait timeout before message send (milliseconds).
 *
 * Used exclusively by sendMessageToClaude() to limit how long we wait
 * for Claude to return to a prompt state before sending a user message.
 * This is separate from CLAUDE_PROMPT_WAIT_TIMEOUT (5000ms, the default
 * for waitForPrompt()) because sendMessageToClaude() may be called
 * shortly after session initialization, where Claude CLI needs additional
 * time to become ready.
 *
 * Relationship to other timeout constants:
 * - CLAUDE_PROMPT_WAIT_TIMEOUT (5000ms): Default for waitForPrompt()
 * - CLAUDE_SEND_PROMPT_WAIT_TIMEOUT (10000ms): sendMessageToClaude() specific
 * - CLAUDE_INIT_TIMEOUT (15000ms): Session initialization timeout
 *
 * @see Issue #187 - Constant unification for sendMessageToClaude timeout
 */
exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT = 10000;
/**
 * Prompt wait polling interval (milliseconds)
 *
 * How frequently we check for prompt state before sending messages.
 * 200ms provides quick response while minimizing CPU usage.
 */
exports.CLAUDE_PROMPT_POLL_INTERVAL = 200;
/**
 * Cached Claude CLI path
 */
let cachedClaudePath = null;
/**
 * Get Claude CLI path dynamically
 * Uses CLAUDE_PATH environment variable if set, otherwise finds via 'which'
 */
async function getClaudePath() {
    // Return cached path if available
    if (cachedClaudePath) {
        return cachedClaudePath;
    }
    // Check environment variable first
    if (process.env.CLAUDE_PATH) {
        cachedClaudePath = process.env.CLAUDE_PATH;
        return cachedClaudePath;
    }
    // Find claude via 'which' command
    try {
        const { stdout } = await execAsync('which claude', { timeout: 5000 });
        cachedClaudePath = stdout.trim();
        return cachedClaudePath;
    }
    catch {
        // Fallback to common paths
        const fallbackPaths = [
            '/opt/homebrew/bin/claude', // macOS Homebrew (Apple Silicon)
            '/usr/local/bin/claude', // macOS Homebrew (Intel) / Linux
            '/usr/bin/claude', // Linux system install
        ];
        for (const path of fallbackPaths) {
            try {
                await execAsync(`test -x "${path}"`, { timeout: 1000 });
                cachedClaudePath = path;
                return cachedClaudePath;
            }
            catch {
                // Path not found, try next
            }
        }
        throw new Error('Claude CLI not found. Set CLAUDE_PATH environment variable or install Claude CLI.');
    }
}
/**
 * Get tmux session name for a worktree
 *
 * @param worktreeId - Worktree ID
 * @returns tmux session name
 *
 * @example
 * ```typescript
 * getSessionName('feature-foo') // => 'mcbd-claude-feature-foo'
 * ```
 */
function getSessionName(worktreeId) {
    return `mcbd-claude-${worktreeId}`;
}
/**
 * Check if Claude is installed and available
 *
 * @returns True if Claude CLI is available
 */
async function isClaudeInstalled() {
    try {
        await execAsync('which claude', { timeout: 5000 });
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Check if Claude session is running
 *
 * @param worktreeId - Worktree ID
 * @returns True if Claude session exists and is running
 *
 * @example
 * ```typescript
 * const running = await isClaudeRunning('feature-foo');
 * if (running) {
 *   console.log('Claude is ready');
 * }
 * ```
 */
async function isClaudeRunning(worktreeId) {
    const sessionName = getSessionName(worktreeId);
    return await (0, tmux_1.hasSession)(sessionName);
}
/**
 * Get Claude session state
 *
 * @param worktreeId - Worktree ID
 * @returns Session state information
 */
async function getClaudeSessionState(worktreeId) {
    const sessionName = getSessionName(worktreeId);
    const isRunning = await (0, tmux_1.hasSession)(sessionName);
    return {
        sessionName,
        isRunning,
        lastActivity: new Date(),
    };
}
/**
 * Wait for session to be at prompt state
 * Polls for prompt detection using CLAUDE_PROMPT_PATTERN (DRY-001)
 *
 * @param sessionName - tmux session name
 * @param timeout - Timeout in milliseconds (default: CLAUDE_PROMPT_WAIT_TIMEOUT)
 * @throws {Error} If prompt is not detected within timeout
 *
 * @example
 * ```typescript
 * await waitForPrompt('mcbd-claude-feature-foo');
 * // Session is now ready to receive input
 * ```
 */
async function waitForPrompt(sessionName, timeout = exports.CLAUDE_PROMPT_WAIT_TIMEOUT) {
    const startTime = Date.now();
    const pollInterval = exports.CLAUDE_PROMPT_POLL_INTERVAL;
    while (Date.now() - startTime < timeout) {
        // Use -50 lines to capture more context including status bars
        const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
        // DRY-001: Use CLAUDE_PROMPT_PATTERN from cli-patterns.ts
        // Strip ANSI escape sequences before pattern matching (Issue #152)
        if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test((0, cli_patterns_1.stripAnsi)(output))) {
            return; // Prompt detected
        }
        await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }
    throw new Error(`Prompt detection timeout (${timeout}ms)`);
}
/**
 * Start a Claude CLI session in tmux
 *
 * @param options - Session options
 * @throws {Error} If Claude CLI is not installed or session creation fails
 *
 * @example
 * ```typescript
 * await startClaudeSession({
 *   worktreeId: 'feature-foo',
 *   worktreePath: '/path/to/worktree',
 * });
 * ```
 */
async function startClaudeSession(options) {
    const { worktreeId, worktreePath } = options;
    // Check if Claude is installed
    const claudeAvailable = await isClaudeInstalled();
    if (!claudeAvailable) {
        throw new Error('Claude CLI is not installed or not in PATH');
    }
    const sessionName = getSessionName(worktreeId);
    // Check if session already exists
    const exists = await (0, tmux_1.hasSession)(sessionName);
    if (exists) {
        console.log(`Claude session ${sessionName} already exists`);
        return;
    }
    try {
        // Create tmux session with large history buffer for Claude output
        await (0, tmux_1.createSession)({
            sessionName,
            workingDirectory: worktreePath,
            historyLimit: 50000,
        });
        // Get Claude CLI path dynamically
        const claudePath = await getClaudePath();
        // Start Claude CLI in interactive mode using dynamically resolved path
        await (0, tmux_1.sendKeys)(sessionName, claudePath, true);
        // Wait for Claude to initialize with dynamic detection (OCP-001)
        // Use constants instead of hardcoded values
        const maxWaitTime = exports.CLAUDE_INIT_TIMEOUT;
        const pollInterval = exports.CLAUDE_INIT_POLL_INTERVAL;
        const startTime = Date.now();
        let initialized = false;
        let trustDialogHandled = false;
        while (Date.now() - startTime < maxWaitTime) {
            await new Promise((resolve) => setTimeout(resolve, pollInterval));
            try {
                const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
                // Claude is ready when we see the prompt (DRY-001)
                // Use CLAUDE_PROMPT_PATTERN from cli-patterns.ts for consistency
                // Strip ANSI escape sequences before pattern matching (Issue #152)
                // Note: CLAUDE_SEPARATOR_PATTERN was removed from initialization check (Issue #187, P1-1)
                // because separator early-detection caused premature returns before prompt was ready
                const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
                if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(cleanOutput)) {
                    // Wait for stability after prompt detection (CONS-007, DOC-001)
                    await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
                    console.log(`Claude initialized in ${Date.now() - startTime}ms`);
                    initialized = true;
                    break;
                }
                // Issue #201: Detect trust dialog and auto-respond with Enter
                // Condition order: CLAUDE_PROMPT_PATTERN (above) is checked first for shortest path
                if (!trustDialogHandled && cli_patterns_1.CLAUDE_TRUST_DIALOG_PATTERN.test(cleanOutput)) {
                    await (0, tmux_1.sendKeys)(sessionName, '', true);
                    trustDialogHandled = true;
                    // TODO: Log output method unification (console.log vs createLogger) to be addressed in a separate Issue (SF-002)
                    console.log('Trust dialog detected, sending Enter to confirm');
                    // Continue polling to wait for prompt detection
                }
            }
            catch {
                // Ignore capture errors during initialization
            }
        }
        // Throw error on timeout instead of silently continuing (CONS-005, IMP-001)
        if (!initialized) {
            throw new Error(`Claude initialization timeout (${exports.CLAUDE_INIT_TIMEOUT}ms)`);
        }
        console.log(`Started Claude session: ${sessionName}`);
    }
    catch (error) {
        throw new Error(`Failed to start Claude session: ${getErrorMessage(error)}`);
    }
}
/**
 * Send a message to Claude CLI
 *
 * @param worktreeId - Worktree ID
 * @param message - Message content to send
 * @throws {Error} If session doesn't exist
 *
 * @example
 * ```typescript
 * await sendMessageToClaude('feature-foo', 'Explain this code');
 * ```
 */
async function sendMessageToClaude(worktreeId, message) {
    const sessionName = getSessionName(worktreeId);
    // Check if session exists
    const exists = await (0, tmux_1.hasSession)(sessionName);
    if (!exists) {
        throw new Error(`Claude session ${sessionName} does not exist. Start the session first.`);
    }
    // Verify prompt state before sending (CONS-006, DRY-001)
    const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
    if (!cli_patterns_1.CLAUDE_PROMPT_PATTERN.test((0, cli_patterns_1.stripAnsi)(output))) {
        // Path B: Prompt not detected - wait for it (P1: throw on timeout)
        await waitForPrompt(sessionName, exports.CLAUDE_SEND_PROMPT_WAIT_TIMEOUT);
    }
    // P0: Stability delay after prompt detection (both Path A and Path B)
    // Same delay as startClaudeSession() to ensure Claude CLI input handler is ready
    // NOTE: This 500ms delay also applies to 2nd+ messages. Currently acceptable since
    // Claude CLI response time (seconds to tens of seconds) dwarfs this overhead.
    // If future batch-send use cases arise, this could be optimized to first-message-only,
    // but that optimization is deferred per YAGNI principle. (ref: F-3)
    await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
    // Send message using sendKeys consistently (CONS-001)
    await (0, tmux_1.sendKeys)(sessionName, message, false);
    await (0, tmux_1.sendKeys)(sessionName, '', true);
    // Issue #212: Detect [Pasted text] and resend Enter for multi-line messages
    // MF-001: Single-line messages skip detection (+0ms overhead)
    if (message.includes('\n')) {
        await (0, pasted_text_helper_1.detectAndResendIfPastedText)(sessionName);
    }
    console.log(`Sent message to Claude session: ${sessionName}`);
}
/**
 * Capture Claude session output
 *
 * @param worktreeId - Worktree ID
 * @param lines - Number of lines to capture (default: 1000)
 * @returns Captured output
 *
 * @example
 * ```typescript
 * const output = await captureClaudeOutput('feature-foo');
 * console.log(output);
 * ```
 */
async function captureClaudeOutput(worktreeId, lines = 1000) {
    const sessionName = getSessionName(worktreeId);
    // Check if session exists
    const exists = await (0, tmux_1.hasSession)(sessionName);
    if (!exists) {
        throw new Error(`Claude session ${sessionName} does not exist`);
    }
    try {
        return await (0, tmux_1.capturePane)(sessionName, { startLine: -lines });
    }
    catch (error) {
        throw new Error(`Failed to capture Claude output: ${getErrorMessage(error)}`);
    }
}
/**
 * Stop a Claude session
 *
 * @param worktreeId - Worktree ID
 * @returns True if session was stopped, false if it didn't exist
 *
 * @example
 * ```typescript
 * await stopClaudeSession('feature-foo');
 * ```
 */
async function stopClaudeSession(worktreeId) {
    const sessionName = getSessionName(worktreeId);
    try {
        // Send Ctrl+D to exit Claude gracefully
        const exists = await (0, tmux_1.hasSession)(sessionName);
        if (exists) {
            await (0, tmux_1.sendKeys)(sessionName, '', false);
            // Send Ctrl+D (ASCII 4)
            await execAsync(`tmux send-keys -t "${sessionName}" C-d`);
            // Wait a moment for Claude to exit
            await new Promise((resolve) => setTimeout(resolve, 500));
        }
        // Kill the tmux session
        const killed = await (0, tmux_1.killSession)(sessionName);
        if (killed) {
            console.log(`✓ Stopped Claude session: ${sessionName}`);
        }
        return killed;
    }
    catch (error) {
        console.error(`Error stopping Claude session: ${getErrorMessage(error)}`);
        return false;
    }
}
/**
 * Restart a Claude session
 *
 * @param options - Session options
 *
 * @example
 * ```typescript
 * await restartClaudeSession({
 *   worktreeId: 'feature-foo',
 *   worktreePath: '/path/to/worktree',
 * });
 * ```
 */
async function restartClaudeSession(options) {
    const { worktreeId } = options;
    // Stop existing session
    await stopClaudeSession(worktreeId);
    // Wait a moment before restarting
    await new Promise((resolve) => setTimeout(resolve, 1000));
    // Start new session
    await startClaudeSession(options);
}
