/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { debugLogger } from '@google/gemini-cli-core';
import clipboardy from 'clipboardy';
import fs from 'node:fs';
/**
 * Checks if a query string potentially represents an '@' command.
 * It triggers if the query starts with '@' or contains '@' preceded by whitespace
 * and followed by a non-whitespace character.
 *
 * @param query The input query string.
 * @returns True if the query looks like an '@' command, false otherwise.
 */
export const isAtCommand = (query) => 
// Check if starts with @ OR has a space, then @
query.startsWith('@') || /\s@/.test(query);
/**
 * Checks if a query string potentially represents an '/' command.
 * It triggers if the query starts with '/' but excludes code comments like '//' and '/*'.
 *
 * @param query The input query string.
 * @returns True if the query looks like an '/' command, false otherwise.
 */
export const isSlashCommand = (query) => {
    if (!query.startsWith('/')) {
        return false;
    }
    // Exclude line comments that start with '//'
    if (query.startsWith('//')) {
        return false;
    }
    // Exclude block comments that start with '/*'
    if (query.startsWith('/*')) {
        return false;
    }
    return true;
};
const ESC = '\u001B';
const BEL = '\u0007';
const ST = '\u001B\\';
const MAX_OSC52_SEQUENCE_BYTES = 100_000;
const OSC52_HEADER = `${ESC}]52;c;`;
const OSC52_FOOTER = BEL;
const MAX_OSC52_BODY_B64_BYTES = MAX_OSC52_SEQUENCE_BYTES -
    Buffer.byteLength(OSC52_HEADER) -
    Buffer.byteLength(OSC52_FOOTER);
const MAX_OSC52_DATA_BYTES = Math.floor(MAX_OSC52_BODY_B64_BYTES / 4) * 3;
// Conservative chunk size for GNU screen DCS passthrough.
const SCREEN_DCS_CHUNK_SIZE = 240;
const pickTty = () => new Promise((resolve) => {
    // /dev/tty is only available on Unix-like systems (Linux, macOS, BSD, etc.)
    if (process.platform !== 'win32') {
        // Prefer the controlling TTY to avoid interleaving escape sequences with piped stdout.
        try {
            const devTty = fs.createWriteStream('/dev/tty');
            // Safety timeout: if /dev/tty doesn't respond quickly, fallback to avoid hanging.
            const timeout = setTimeout(() => {
                // Remove listeners to prevent them from firing after timeout.
                devTty.removeAllListeners('open');
                devTty.removeAllListeners('error');
                devTty.destroy();
                resolve(getStdioTty());
            }, 100);
            // If we can't open it (e.g. sandbox), we'll get an error.
            // We wait for 'open' to confirm it's usable, or 'error' to fallback.
            // If it opens, we resolve with the stream.
            devTty.once('open', () => {
                clearTimeout(timeout);
                devTty.removeAllListeners('error');
                // Prevent future unhandled 'error' events from crashing the process
                devTty.on('error', () => { });
                resolve({ stream: devTty, closeAfter: true });
            });
            // If it errors immediately (or quickly), we fallback.
            devTty.once('error', () => {
                clearTimeout(timeout);
                devTty.removeAllListeners('open');
                resolve(getStdioTty());
            });
            return;
        }
        catch {
            // fall through - synchronous failure
        }
    }
    resolve(getStdioTty());
});
const getStdioTty = () => {
    // On Windows, prioritize stdout to prevent shell-specific formatting (e.g., PowerShell's
    // red stderr) from corrupting the raw escape sequence payload.
    if (process.platform === 'win32') {
        if (process.stdout?.isTTY)
            return { stream: process.stdout, closeAfter: false };
        if (process.stderr?.isTTY)
            return { stream: process.stderr, closeAfter: false };
        return null;
    }
    // On non-Windows platforms, prioritize stderr to avoid polluting stdout,
    // preserving it for potential redirection or piping.
    if (process.stderr?.isTTY)
        return { stream: process.stderr, closeAfter: false };
    if (process.stdout?.isTTY)
        return { stream: process.stdout, closeAfter: false };
    return null;
};
const inTmux = () => Boolean(process.env['TMUX'] || (process.env['TERM'] ?? '').startsWith('tmux'));
const inScreen = () => Boolean(process.env['STY'] || (process.env['TERM'] ?? '').startsWith('screen'));
const isSSH = () => Boolean(process.env['SSH_TTY'] ||
    process.env['SSH_CONNECTION'] ||
    process.env['SSH_CLIENT']);
const isWSL = () => Boolean(process.env['WSL_DISTRO_NAME'] ||
    process.env['WSLENV'] ||
    process.env['WSL_INTEROP']);
const isWindowsTerminal = () => process.platform === 'win32' && Boolean(process.env['WT_SESSION']);
const isDumbTerm = () => (process.env['TERM'] ?? '') === 'dumb';
const shouldUseOsc52 = (tty) => Boolean(tty) && !isDumbTerm() && (isSSH() || isWSL() || isWindowsTerminal());
const safeUtf8Truncate = (buf, maxBytes) => {
    if (buf.length <= maxBytes)
        return buf;
    let end = maxBytes;
    // Back up to the start of a UTF-8 code point if we cut through a continuation byte (10xxxxxx).
    while (end > 0 && (buf[end - 1] & 0b1100_0000) === 0b1000_0000)
        end--;
    return buf.subarray(0, end);
};
const buildOsc52 = (text) => {
    const raw = Buffer.from(text, 'utf8');
    const safe = safeUtf8Truncate(raw, MAX_OSC52_DATA_BYTES);
    const b64 = safe.toString('base64');
    return `${OSC52_HEADER}${b64}${OSC52_FOOTER}`;
};
const wrapForTmux = (seq) => {
    // Double ESC bytes in payload without a control-character regex.
    const doubledEsc = seq.split(ESC).join(ESC + ESC);
    return `${ESC}Ptmux;${doubledEsc}${ST}`;
};
const wrapForScreen = (seq) => {
    let out = '';
    for (let i = 0; i < seq.length; i += SCREEN_DCS_CHUNK_SIZE) {
        out += `${ESC}P${seq.slice(i, i + SCREEN_DCS_CHUNK_SIZE)}${ST}`;
    }
    return out;
};
const writeAll = (stream, data) => new Promise((resolve, reject) => {
    // On Windows, writing directly to the underlying file descriptor bypasses
    // application-level stream interception (e.g., by the Ink UI framework).
    // This ensures the raw OSC-52 escape sequence reaches the terminal host uncorrupted.
    const fd = stream.fd;
    if (process.platform === 'win32' &&
        typeof fd === 'number' &&
        (stream === process.stdout || stream === process.stderr)) {
        try {
            fs.writeSync(fd, data);
            resolve();
            return;
        }
        catch (e) {
            debugLogger.warn('Direct write to TTY failed, falling back to stream write', e);
        }
    }
    const onError = (err) => {
        cleanup();
        reject(err);
    };
    const onDrain = () => {
        cleanup();
        resolve();
    };
    const cleanup = () => {
        stream.off('error', onError);
        stream.off('drain', onDrain);
        // Writable.write() handlers may not emit 'drain' if the first write succeeded.
    };
    stream.once('error', onError);
    if (stream.write(data)) {
        cleanup();
        resolve();
    }
    else {
        stream.once('drain', onDrain);
    }
});
// Copies a string snippet to the clipboard with robust OSC-52 support.
export const copyToClipboard = async (text) => {
    if (!text)
        return;
    const tty = await pickTty();
    if (shouldUseOsc52(tty)) {
        const osc = buildOsc52(text);
        const payload = inTmux()
            ? wrapForTmux(osc)
            : inScreen()
                ? wrapForScreen(osc)
                : osc;
        await writeAll(tty.stream, payload);
        if (tty.closeAfter) {
            tty.stream.end();
        }
        return;
    }
    // Local / non-TTY fallback
    await clipboardy.write(text);
};
export const getUrlOpenCommand = () => {
    // --- Determine the OS-specific command to open URLs ---
    let openCmd;
    switch (process.platform) {
        case 'darwin':
            openCmd = 'open';
            break;
        case 'win32':
            openCmd = 'start';
            break;
        case 'linux':
            openCmd = 'xdg-open';
            break;
        default:
            // Default to xdg-open, which appears to be supported for the less popular operating systems.
            openCmd = 'xdg-open';
            debugLogger.warn(`Unknown platform: ${process.platform}. Attempting to open URLs with: ${openCmd}.`);
            break;
    }
    return openCmd;
};
/**
 * Determines if a slash command should auto-execute when selected.
 *
 * All built-in commands have autoExecute explicitly set to true or false.
 * Custom commands (.toml files) and extension commands without this flag
 * will default to false (safe default - won't auto-execute).
 *
 * @param command The slash command to check
 * @returns true if the command should auto-execute on Enter
 */
export function isAutoExecutableCommand(command) {
    if (!command) {
        return false;
    }
    // Simply return the autoExecute flag value, defaulting to false if undefined
    return command.autoExecute ?? false;
}
//# sourceMappingURL=commandUtils.js.map