/**
 * Shell detection and environment setup for bash command execution.
 *
 * Resolves the correct shell binary across platforms (Git Bash on Windows,
 * /bin/bash or sh on Unix), builds a PATH that includes bundled tool binaries
 * and Python, and provides output sanitization for binary-safe display.
 */
import { spawnSync } from 'child_process';
import { existsSync } from 'node:fs';
import { delimiter } from 'node:path';
import { getBinDir, getPythonPathDirs, getSettingsPath } from '../../config.js';
let cachedShellConfig = null;
/**
 * Clear cached shell config so the next getShellConfig() call re-detects.
 * Call after installing Git so subsequent commands use the new bash.
 */
export function resetShellConfig() {
    cachedShellConfig = null;
}
/**
 * Verify a bash executable actually works by running a simple command.
 * Guards against WSL stubs that fall back to cmd.exe when WSL isn't configured.
 */
function isBashWorking(bashPath) {
    try {
        const result = spawnSync(bashPath, ['-c', 'echo __bash_ok__'], {
            encoding: 'utf-8',
            timeout: 5000,
            stdio: ['ignore', 'pipe', 'pipe']
        });
        return result.status === 0 && result.stdout.includes('__bash_ok__');
    }
    catch {
        return false;
    }
}
/** Find a working bash executable on PATH (cross-platform). */
function findBashOnPath() {
    if (process.platform === 'win32') {
        // Windows: Use 'where' and verify file exists (where can return non-existent paths)
        try {
            const result = spawnSync('where', ['bash.exe'], { encoding: 'utf-8', timeout: 5000 });
            if (result.status === 0 && result.stdout) {
                // Try each match — skip WSL stubs and broken installs
                for (const match of result.stdout.trim().split(/\r?\n/)) {
                    const trimmed = match.trim();
                    if (trimmed && existsSync(trimmed) && isBashWorking(trimmed)) {
                        return trimmed;
                    }
                }
            }
        }
        catch {
            // Ignore errors
        }
        return null;
    }
    // Unix: Use 'which' and trust its output (handles Termux and special filesystems)
    try {
        const result = spawnSync('which', ['bash'], { encoding: 'utf-8', timeout: 5000 });
        if (result.status === 0 && result.stdout) {
            const firstMatch = result.stdout.trim().split(/\r?\n/)[0];
            if (firstMatch) {
                return firstMatch;
            }
        }
    }
    catch {
        // Ignore errors
    }
    return null;
}
/**
 * Get shell configuration based on platform.
 * Resolution order:
 * 1. User-specified shellPath in settings.json
 * 2. On Windows: Git Bash in known locations, then bash on PATH
 * 3. On Unix: /bin/bash, then bash on PATH, then fallback to sh
 */
export function getShellConfig(customShellPath) {
    if (cachedShellConfig) {
        return cachedShellConfig;
    }
    // 1. Check user-specified shell path
    if (customShellPath) {
        // Reject cmd.exe / powershell — the bash tool requires a POSIX shell
        const shellBasename = customShellPath.replace(/.*[/\\]/, '').toLowerCase();
        if (['cmd.exe', 'cmd', 'powershell.exe', 'powershell', 'pwsh.exe', 'pwsh'].includes(shellBasename)) {
            throw new Error(`shellPath is set to "${customShellPath}" but Shortcut requires a bash-compatible shell.\n` +
                `Please remove shellPath from ${getSettingsPath()} or set it to a bash executable.\n` +
                `Install Git for Windows if needed: https://git-scm.com/download/win`);
        }
        if (existsSync(customShellPath)) {
            cachedShellConfig = { shell: customShellPath, args: ['-c'] };
            return cachedShellConfig;
        }
        throw new Error(`Custom shell path not found: ${customShellPath}\nPlease update shellPath in ${getSettingsPath()}`);
    }
    if (process.platform === 'win32') {
        // 2. Try Git Bash in known locations
        const paths = [];
        const programFiles = process.env.ProgramFiles;
        if (programFiles) {
            paths.push(`${programFiles}\\Git\\bin\\bash.exe`);
        }
        const programFilesX86 = process.env['ProgramFiles(x86)'];
        if (programFilesX86) {
            paths.push(`${programFilesX86}\\Git\\bin\\bash.exe`);
        }
        // Per-user Git install (winget / "Install for current user only")
        const localAppData = process.env.LOCALAPPDATA;
        if (localAppData) {
            paths.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
        }
        for (const path of paths) {
            if (existsSync(path)) {
                cachedShellConfig = { shell: path, args: ['-c'] };
                return cachedShellConfig;
            }
        }
        // 3. Fallback: search bash.exe on PATH (Cygwin, MSYS2, WSL, etc.)
        const bashOnPath = findBashOnPath();
        if (bashOnPath) {
            cachedShellConfig = { shell: bashOnPath, args: ['-c'] };
            return cachedShellConfig;
        }
        // 4. No bash at all — fall back to cmd.exe for bootstrapping.
        // Do NOT cache this result so we auto-detect bash once Git is installed.
        return { shell: 'cmd.exe', args: ['/c'] };
    }
    // Unix: try /bin/bash, then bash on PATH, then fallback to sh
    if (existsSync('/bin/bash')) {
        cachedShellConfig = { shell: '/bin/bash', args: ['-c'] };
        return cachedShellConfig;
    }
    const bashOnPath = findBashOnPath();
    if (bashOnPath) {
        cachedShellConfig = { shell: bashOnPath, args: ['-c'] };
        return cachedShellConfig;
    }
    cachedShellConfig = { shell: 'sh', args: ['-c'] };
    return cachedShellConfig;
}
/**
 * Build the environment for shell subprocesses.
 *
 * Prepends bundled binary and Python directories to PATH, and sets
 * PYTHONIOENCODING to UTF-8 to prevent codec crashes on Windows.
 */
export function getShellEnv() {
    const binDir = getBinDir();
    const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === 'path') ?? 'PATH';
    const currentPath = process.env[pathKey] ?? '';
    const pathEntries = currentPath.split(delimiter).filter(Boolean);
    // Prepend directories that should be on PATH for shell commands:
    // - binDir: bundled rg, fd, uv binaries
    // - pythonPathDirs: Python executable + pip-installed entry points (platform-specific)
    const allDirs = [binDir, ...getPythonPathDirs()];
    const seen = new Set(pathEntries);
    const dirsToAdd = allDirs.filter((d) => {
        if (!d || seen.has(d))
            return false;
        seen.add(d);
        return true;
    });
    const updatedPath = dirsToAdd.length > 0
        ? [...dirsToAdd, currentPath].filter(Boolean).join(delimiter)
        : currentPath;
    return {
        ...process.env,
        [pathKey]: updatedPath,
        // Ensure Python subprocesses emit UTF-8 on stdout/stderr, not the Windows
        // default (cp1252) which crashes on characters like ☐ U+2610.
        PYTHONIOENCODING: process.env.PYTHONIOENCODING ?? 'utf-8'
    };
}
/**
 * Sanitize binary output for display/storage.
 * Removes characters that crash string-width or cause display issues:
 * - Control characters (except tab, newline, carriage return)
 * - Lone surrogates
 * - Unicode Format characters (crash string-width due to a bug)
 * - Characters with undefined code points
 */
export function sanitizeBinaryOutput(str) {
    // Use Array.from to properly iterate over code points (not code units)
    // This handles surrogate pairs correctly and catches edge cases where
    // codePointAt() might return undefined
    return Array.from(str)
        .filter((char) => {
        // Filter out characters that cause string-width to crash
        // This includes:
        // - Unicode format characters
        // - Lone surrogates (already filtered by Array.from)
        // - Control chars except \t \n \r
        // - Characters with undefined code points
        const code = char.codePointAt(0);
        // Skip if code point is undefined (edge case with invalid strings)
        if (code === undefined)
            return false;
        // Allow tab, newline, carriage return
        if (code === 0x09 || code === 0x0a || code === 0x0d)
            return true;
        // Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0x0d)
        if (code <= 0x1f)
            return false;
        // Filter out Unicode format characters
        if (code >= 0xfff9 && code <= 0xfffb)
            return false;
        return true;
    })
        .join('');
}
//# sourceMappingURL=bash-runtime.js.map