"use strict";
/**
 * OpenCode CLI tool implementation
 * Issue #379: Provides integration with OpenCode TUI in interactive mode
 *
 * @remarks Follows the same tmux-based pattern as Claude/Codex/Gemini/VibeLocal tools.
 * - startSession: launches `opencode` TUI in tmux
 * - sendMessage: sends text via tmux send-keys + Enter
 * - killSession: sends `/exit` command then falls back to tmux kill-session
 * - interrupt(): inherits BaseCLITool default (Escape key) [D2-008]
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenCodeTool = exports.OPENCODE_INIT_WAIT_MS = exports.OPENCODE_PANE_HEIGHT = exports.OPENCODE_EXIT_COMMAND = void 0;
const base_1 = require("./base");
const tmux_1 = require("../tmux/tmux");
const pasted_text_helper_1 = require("../pasted-text-helper");
const tmux_capture_cache_1 = require("../tmux/tmux-capture-cache");
const opencode_config_1 = require("./opencode-config");
const child_process_1 = require("child_process");
const util_1 = require("util");
const logger_1 = require("../../lib/logger");
const logger = (0, logger_1.createLogger)('cli-tools/opencode');
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
/**
 * Extract error message from unknown error type (DRY)
 * Same pattern as claude-session.ts / codex.ts / gemini.ts / vibe-local.ts.
 * A shared version exists in src/lib/errors.ts (getErrorMessage), but CLI tool
 * modules use local copies to avoid importing the server-side error module.
 * [D1-002] Future refactoring candidate: extract to BaseCLITool or a shared util.
 */
function getErrorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
/** OpenCode TUI graceful exit command [D1-006] */
exports.OPENCODE_EXIT_COMMAND = '/exit';
/**
 * OpenCode tmux pane height (rows).
 * Set to 200 to expand the TUI content area (~190 visible lines),
 * allowing most responses to be captured in a single tmux capture-pane.
 * OpenCode runs in alternate screen mode with no scrollback buffer,
 * so only visible rows are capturable.
 */
exports.OPENCODE_PANE_HEIGHT = 200;
/**
 * Wait for OpenCode TUI to initialize after launch.
 * Set to 15000ms to accommodate GPU model loading via Ollama.
 */
exports.OPENCODE_INIT_WAIT_MS = 15000;
/**
 * OpenCode CLI tool implementation
 * Manages OpenCode interactive sessions using tmux
 */
class OpenCodeTool extends base_1.BaseCLITool {
    id = 'opencode';
    name = 'OpenCode';
    command = 'opencode';
    // interrupt() is inherited from BaseCLITool (Escape key) [D2-008]
    // OpenCode TUI supports Escape for interruption ("esc interrupt" display)
    /**
     * Check if OpenCode session is running for a worktree
     */
    async isRunning(worktreeId) {
        const sessionName = this.getSessionName(worktreeId);
        return await (0, tmux_1.hasSession)(sessionName);
    }
    /**
     * Start a new OpenCode session for a worktree
     * Launches `opencode` TUI in interactive mode within tmux
     *
     * @param worktreeId - Worktree ID
     * @param worktreePath - Worktree path
     */
    async startSession(worktreeId, worktreePath) {
        const opencodeAvailable = await this.isInstalled();
        if (!opencodeAvailable) {
            throw new Error('OpenCode is not installed or not in PATH');
        }
        const sessionName = this.getSessionName(worktreeId);
        const exists = await (0, tmux_1.hasSession)(sessionName);
        if (exists) {
            logger.info('opencode-session-sessionname');
            return;
        }
        try {
            // Generate opencode.json if not present (non-fatal on failure)
            await (0, opencode_config_1.ensureOpencodeConfig)(worktreePath);
            // Create tmux session with large history buffer
            await (0, tmux_1.createSession)({
                sessionName,
                workingDirectory: worktreePath,
                historyLimit: 50000,
            });
            // Wait a moment for the session to be created
            await new Promise((resolve) => setTimeout(resolve, 100));
            // Resize tmux window to 80 columns (hide sidebar for clean capture-pane output)
            // [SEC-001] Uses execFile (not exec) to prevent shell meta-character injection via sessionName
            try {
                await execFileAsync('tmux', [
                    'resize-window', '-t', sessionName,
                    '-x', '80', '-y', String(exports.OPENCODE_PANE_HEIGHT),
                ]);
            }
            catch {
                // Non-fatal: resize may fail in some environments
            }
            // Start OpenCode TUI
            await (0, tmux_1.sendKeys)(sessionName, 'opencode', true);
            // Wait for OpenCode to initialize (GPU model loading via Ollama)
            await new Promise((resolve) => setTimeout(resolve, exports.OPENCODE_INIT_WAIT_MS));
            logger.info('started-opencode-session:sessionname');
        }
        catch (error) {
            const errorMessage = getErrorMessage(error);
            throw new Error(`Failed to start OpenCode session: ${errorMessage}`);
        }
    }
    /**
     * Send a message to OpenCode interactive session
     * [D1-004] Same pattern as Codex/Gemini/VibeLocal (future Template Method candidate)
     *
     * @param worktreeId - Worktree ID
     * @param message - Message to send
     */
    async sendMessage(worktreeId, message) {
        const sessionName = this.getSessionName(worktreeId);
        const exists = await (0, tmux_1.hasSession)(sessionName);
        if (!exists) {
            throw new Error(`OpenCode session ${sessionName} does not exist. Start the session first.`);
        }
        try {
            // Send message to OpenCode (without Enter)
            await (0, tmux_1.sendKeys)(sessionName, message, false);
            // Wait a moment for the text to be typed
            await new Promise((resolve) => setTimeout(resolve, 100));
            // Send Enter key separately
            await (0, tmux_1.sendSpecialKey)(sessionName, 'C-m');
            // Wait a moment for the message to be processed
            await new Promise((resolve) => setTimeout(resolve, 200));
            // Detect [Pasted text] and resend Enter for multi-line messages
            if (message.includes('\n')) {
                await (0, pasted_text_helper_1.detectAndResendIfPastedText)(sessionName);
            }
            // Issue #405: Invalidate cache after sending message
            (0, tmux_capture_cache_1.invalidateCache)(sessionName);
            logger.info('sent-message-to-opencode-session:session');
        }
        catch (error) {
            const errorMessage = getErrorMessage(error);
            throw new Error(`Failed to send message to OpenCode: ${errorMessage}`);
        }
    }
    /**
     * Kill OpenCode session with graceful shutdown.
     *
     * Shutdown sequence [D1-006, D1-007]:
     * 1. Check if session exists
     * 2. If exists: send `/exit` TUI command for graceful shutdown
     * 3. Wait 2s for OpenCode to process the exit command
     * 4. Re-check session: if still running, force-kill via tmux kill-session
     * 5. If session did not exist: attempt kill anyway (cleanup stale sessions)
     *
     * @param worktreeId - Worktree ID
     */
    async killSession(worktreeId) {
        const sessionName = this.getSessionName(worktreeId);
        try {
            // Step 1: Check if the tmux session currently exists
            const exists = await (0, tmux_1.hasSession)(sessionName);
            if (exists) {
                // Step 2: Send /exit command for graceful TUI shutdown [D1-006]
                await (0, tmux_1.sendKeys)(sessionName, exports.OPENCODE_EXIT_COMMAND, true);
                // Step 3: Wait for OpenCode to process the exit command
                await new Promise((resolve) => setTimeout(resolve, 2000));
                // Step 4: Check if session still exists; force-kill if needed [D1-007]
                const stillExists = await (0, tmux_1.hasSession)(sessionName);
                if (stillExists) {
                    await (0, tmux_1.killSession)(sessionName);
                }
            }
            else {
                // Step 5: Session does not exist, attempt kill anyway (cleanup stale tmux sessions)
                await (0, tmux_1.killSession)(sessionName);
            }
            // Issue #405: Invalidate cache after session kill
            (0, tmux_capture_cache_1.invalidateCache)(sessionName);
            logger.info('stopped-opencode-session:sessionname');
        }
        catch (error) {
            logger.error('session:stop-failed', { error: getErrorMessage(error) });
            throw error;
        }
    }
}
exports.OpenCodeTool = OpenCodeTool;
