/**
 * Terminal UI utilities for formatted, colorized output
 *
 * Provides semantic helpers for console output with automatic icons and colors.
 * Uses Bun's built-in color support and ANSI escape codes.
 */
import { stringWidth } from 'bun';
import { resolve } from 'node:path';
import { colorize } from 'json-colorizer';
import enquirer from 'enquirer';
import { projectList } from '@agentuity/server';
import * as readline from 'readline';
import { getExitCode } from './errors';
import { maskSecret } from './env-util';
import { getExecutingAgent } from './agent-detection';
// Install global exit handler to always restore terminal cursor
// This ensures cursor is restored even when process.exit() is called directly
let exitHandlerInstalled = false;
function ensureCursorRestoration() {
    if (exitHandlerInstalled)
        return;
    exitHandlerInstalled = true;
    const restoreCursor = () => {
        // Only write ANSI escape sequences when stderr is a real terminal.
        // Writing to non-TTY streams (pipes, command substitution, etc.)
        // pollutes captured output with invisible control characters.
        if (!process.stderr.isTTY) {
            return;
        }
        // Skip cursor restoration in CI - terminals don't support these sequences
        if (process.env.CI) {
            return;
        }
        // Skip cursor restoration when running from an AI coding agent
        if (getExecutingAgent()) {
            return;
        }
        // Restore cursor visibility
        process.stderr.write('\x1B[?25h');
    };
    // Handle process exit
    process.on('exit', restoreCursor);
}
// Install handler immediately when module loads
ensureCursorRestoration();
// Re-export maskSecret for convenience
export { maskSecret };
// Export new TUI components
export { createPrompt, PromptFlow } from './tui/prompt';
export { group } from './tui/group';
export { note, drawBox, errorBox, warningBox } from './tui/box';
export { symbols } from './tui/symbols';
export { colors as tuiColors } from './tui/colors';
// Icons - use plain text alternatives when running from an AI coding agent
function getIcons() {
    if (getExecutingAgent()) {
        return {
            success: '[OK]',
            error: '[ERROR]',
            warning: '[WARN]',
            info: '[INFO]',
            arrow: '->',
            bullet: '-',
        };
    }
    return {
        success: '✓',
        error: '✗',
        warning: '⚠',
        info: 'ℹ',
        arrow: '→',
        bullet: '•',
    };
}
// Export ICONS as a getter for backward compatibility
// Proxy with full traps for enumeration and serialization support
export const ICONS = new Proxy({}, {
    get(_target, prop) {
        return getIcons()[prop];
    },
    has(_target, prop) {
        return prop in getIcons();
    },
    ownKeys() {
        return Object.keys(getIcons());
    },
    getOwnPropertyDescriptor(_target, prop) {
        const icons = getIcons();
        if (prop in icons) {
            return { configurable: true, enumerable: true, value: icons[prop] };
        }
    },
});
/**
 * Check if we should treat the terminal as TTY-like for interactive output
 * (real TTY on stdout or stderr, or FORCE_COLOR set by fork wrapper).
 * Returns false in CI environments since CI terminals often don't support
 * cursor control sequences reliably.
 * Returns false when running from an AI coding agent (no interactive prompts/spinners).
 */
export function isTTYLike() {
    if (process.env.CI) {
        return false;
    }
    // Disable interactive features when running from an AI coding agent
    if (getExecutingAgent()) {
        return false;
    }
    return !!process.stdout.isTTY || !!process.stderr.isTTY || process.env.FORCE_COLOR === '1';
}
/**
 * Get terminal width, respecting COLUMNS env var for piped processes
 */
export function getTerminalWidth(defaultWidth = 80) {
    if (process.stdout.columns) {
        return process.stdout.columns;
    }
    if (process.env.COLUMNS) {
        const cols = parseInt(process.env.COLUMNS, 10);
        if (!Number.isNaN(cols) && cols > 0) {
            return cols;
        }
    }
    return defaultWidth;
}
export function shouldUseColors() {
    // FORCE_COLOR overrides TTY detection (used by fork wrapper)
    if (process.env.FORCE_COLOR === '1') {
        return true;
    }
    // Disable colors when running from an AI coding agent (cleaner output for parsing)
    if (getExecutingAgent()) {
        return false;
    }
    return (!process.env.NO_COLOR &&
        !process.env.CI &&
        process.env.TERM !== 'dumb' &&
        !!process.stdout.isTTY);
}
// Color definitions (light/dark adaptive)
// Note: We use direct ANSI codes instead of Bun.color() because Bun.color()
// returns corrupted sequences when stdout is not a TTY (even with FORCE_COLOR=1)
function getColors() {
    const USE_COLORS = shouldUseColors();
    if (!USE_COLORS) {
        return {
            success: { light: '', dark: '' },
            error: { light: '', dark: '' },
            warning: { light: '', dark: '' },
            info: { light: '', dark: '' },
            muted: { light: '', dark: '' },
            bold: { light: '', dark: '' },
            link: { light: '', dark: '' },
            primary: { light: '', dark: '' },
            reset: '',
        };
    }
    return {
        success: {
            light: '\x1b[32m', // green
            dark: '\x1b[92m', // bright green
        },
        error: {
            light: '\x1b[31m', // red
            dark: '\x1b[91m', // bright red
        },
        warning: {
            light: '\x1b[33m', // yellow
            dark: '\x1b[93m', // bright yellow
        },
        info: {
            light: '\x1b[36m', // dark cyan
            dark: '\x1b[96m', // bright cyan
        },
        muted: {
            light: '\x1b[90m', // gray
            dark: '\x1b[90m', // gray
        },
        bold: {
            light: '\x1b[1m',
            dark: '\x1b[1m',
        },
        link: {
            light: '\x1b[34;4m', // blue underline
            dark: '\x1b[94;4m', // bright blue underline
        },
        primary: {
            light: '\x1b[30m', // black
            dark: '\x1b[97m', // white
        },
        reset: '\x1b[0m',
    };
}
let currentColorScheme = process.env.CI ? 'light' : 'dark';
export function setColorScheme(scheme) {
    currentColorScheme = scheme;
    process.env.COLOR_SCHEME = scheme;
}
export function isDarkMode() {
    return currentColorScheme === 'dark';
}
export function getColor(colorKey) {
    const COLORS = getColors();
    const color = COLORS[colorKey];
    if (typeof color === 'string') {
        return color;
    }
    return color[currentColorScheme];
}
/**
 * Color helpers that return colored strings (for inline use, no icons)
 */
export function colorSuccess(text) {
    const color = getColor('success');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
export function colorError(text) {
    const color = getColor('error');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
export function colorWarning(text) {
    const color = getColor('warning');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
export function colorInfo(text) {
    const color = getColor('info');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
export function colorMuted(text) {
    const color = getColor('muted');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
export function colorPrimary(text) {
    const color = getColor('primary');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
/**
 * Get the appropriate color function for a log severity level
 */
export function getSeverityColor(severity) {
    switch (severity.toUpperCase()) {
        case 'ERROR':
            return colorError;
        case 'WARN':
            return colorWarning;
        case 'INFO':
            return colorInfo;
        case 'DEBUG':
            return colorMuted;
        default:
            return (text) => text;
    }
}
/**
 * Print a success message with a green checkmark
 */
export function success(message) {
    const color = getColor('success');
    const reset = getColor('reset');
    if (process.stderr.isTTY) {
        // Clear line first to ensure no leftover content from previous output
        process.stderr.write(`\r\x1b[2K${color}${ICONS.success} ${message}${reset}\n`);
    }
    else {
        // No ANSI control sequences for non-TTY streams (pipes, command substitution)
        process.stderr.write(`${ICONS.success} ${message}\n`);
    }
}
/**
 * Print an error message with a red X
 */
export function error(message) {
    const color = getColor('error');
    const reset = getColor('reset');
    process.stderr.write(`${color}${ICONS.error} ${message}${reset}\n`);
}
/**
 * Print an error message with a red X and then exit
 */
export function fatal(message, errorCode) {
    const color = getColor('error');
    const reset = getColor('reset');
    process.stderr.write(`${color}${ICONS.error} ${message}${reset}\n`);
    if (errorCode) {
        const exitCode = getExitCode(errorCode);
        process.exit(exitCode);
    }
    else {
        process.exit(1);
    }
}
/**
 * Print a warning message with a yellow warning icon
 */
export function warning(message, asError = false) {
    const color = asError ? getColor('error') : getColor('warning');
    const reset = getColor('reset');
    process.stderr.write(`${color}${ICONS.warning} ${message}${reset}\n`);
}
/**
 * Print an info message with a cyan info icon
 */
export function info(message) {
    const color = getColor('info');
    const reset = getColor('reset');
    process.stderr.write(`${color}${ICONS.info} ${message}${reset}\n`);
}
/**
 * Format text in muted/gray color
 */
export function muted(text) {
    const color = getColor('muted');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
/**
 * Format text in warn color
 */
export function warn(text) {
    const color = getColor('warning');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
/**
 * Format text in bold
 */
export function bold(text) {
    const color = getColor('bold');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
/**
 * Format text with white bold (or inverse for light mode)
 * Used for table headings
 */
export function heading(text) {
    const color = getColor('info');
    const reset = getColor('reset');
    return `${color}${text}${reset}`;
}
/**
 * Format text as a link (blue and underlined)
 */
export function link(url, title, color = getColor('link')) {
    const reset = getColor('reset');
    // Check if terminal supports hyperlinks (OSC 8) and colors are enabled
    if (shouldUseColors() && supportsHyperlinks()) {
        return `\x1b]8;;${url}\x07${color}${title ?? url}${reset}\x1b]8;;\x07`;
    }
    return `${color}${url}${reset}`;
}
/**
 * Check if terminal supports OSC 8 hyperlinks
 */
export function supportsHyperlinks() {
    // No hyperlink support without a TTY
    if (!process.stdout.isTTY) {
        return false;
    }
    const term = process.env.TERM || '';
    const termProgram = process.env.TERM_PROGRAM || '';
    const wtSession = process.env.WT_SESSION || '';
    // Known terminal programs that support OSC 8
    return (termProgram.includes('iTerm.app') ||
        termProgram.includes('WezTerm') ||
        termProgram.includes('ghostty') ||
        termProgram.includes('Apple_Terminal') ||
        termProgram.includes('Hyper') ||
        term.includes('xterm-kitty') ||
        wtSession !== '' // Windows Terminal
    );
}
export function fileUrl(file, line, col) {
    const abs = resolve(file);
    // VS Code understands both file:// and vscode://,
    // but vscode:// allows line + column everywhere
    let url = `vscode://file/${abs}`;
    if (line != null) {
        url += `:${line}`;
        if (col != null)
            url += `:${col}`;
    }
    return url;
}
export function sourceLink(file, line, col, display, color) {
    const label = `${file}:${line}:${col}`;
    const url = fileUrl(file, line, col);
    if (supportsHyperlinks()) {
        return link(url, display ?? label, color);
    }
    // Cmd/Ctrl-click fallback
    if (color) {
        return color + label + getColor('reset');
    }
    return label;
}
/**
 * Print a bulleted list item
 */
export function bullet(message) {
    process.stderr.write(`${ICONS.bullet} ${message}\n`);
}
/**
 * Print an arrow item (for showing next steps)
 */
export function arrow(message) {
    process.stderr.write(`${ICONS.arrow} ${message}\n`);
}
/**
 * Print a blank line
 */
export function newline() {
    process.stderr.write('\n');
}
/**
 * Print a section header without any prefix icon
 */
export function header(message) {
    const color = getColor('bold');
    const reset = getColor('reset');
    process.stderr.write(`${color}${message}${reset}\n`);
}
/**
 * Print plain text output without any prefix or icon
 * Use for primary command output that shouldn't have semantic formatting
 */
export function output(message) {
    console.log(message);
}
/**
 * Get the display width of a string, handling ANSI codes and OSC 8 hyperlinks
 *
 * Note: Bun.stringWidth() counts OSC 8 hyperlink escape sequences in the width,
 * which causes incorrect alignment. We strip OSC 8 codes first, then use Bun.stringWidth()
 * to handle regular ANSI codes and unicode characters correctly.
 */
export function getDisplayWidth(str) {
    // Remove OSC-8 hyperlink sequences using Unicode escapes (\u001b = ESC, \u0007 = BEL) to satisfy linter
    // eslint-disable-next-line no-control-regex
    const withoutOSC8 = str.replace(/\u001b\]8;;[^\u0007]*\u0007/g, '');
    return Bun.stringWidth(withoutOSC8);
}
/**
 * Strip all ANSI escape sequences from a string
 */
export function stripAnsi(str) {
    /* eslint-disable no-control-regex */
    return str
        .replace(/\u001b\[[0-9;]*m/g, '') // SGR sequences (colors, bold, etc.)
        .replace(/\u001b\[\?[0-9;]*[a-zA-Z]/g, '') // DEC private mode (cursor show/hide, etc.)
        .replace(/\u001b\]8;;[^\u0007]*\u0007/g, ''); // OSC 8 hyperlinks
    /* eslint-enable no-control-regex */
}
/**
 * Truncate a string to a maximum display width, handling ANSI codes and Unicode correctly
 * Preserves ANSI escape sequences and doesn't break multi-byte characters or grapheme clusters
 */
export function truncateToWidth(str, maxWidth, ellipsis = '...') {
    const totalWidth = getDisplayWidth(str);
    if (totalWidth <= maxWidth) {
        return str;
    }
    // Strip ANSI to get visible text
    const visible = stripAnsi(str);
    // Use Intl.Segmenter for grapheme-aware iteration
    const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
    const segments = Array.from(segmenter.segment(visible));
    // Find the cutoff point by accumulating display width
    let currentWidth = 0;
    let cutIndex = 0;
    const targetWidth = maxWidth - ellipsis.length;
    for (let i = 0; i < segments.length; i++) {
        const seg = segments[i];
        if (!seg)
            continue;
        const segment = seg.segment;
        const segmentWidth = Bun.stringWidth(segment);
        if (currentWidth + segmentWidth > targetWidth) {
            break;
        }
        currentWidth += segmentWidth;
        cutIndex = seg.index + segment.length;
    }
    // Now reconstruct with ANSI codes preserved
    // Walk through original string and copy characters + ANSI codes until we hit cutIndex in visible content
    let result = '';
    let visibleIndex = 0;
    let i = 0;
    while (i < str.length && visibleIndex < cutIndex) {
        // Check for ANSI escape sequence
        if (str[i] === '\u001b') {
            /* eslint-disable no-control-regex */
            // Copy entire SGR sequence (colors, bold, etc.)
            const match = str.slice(i).match(/^\u001b\[[0-9;]*m/);
            if (match) {
                result += match[0];
                i += match[0].length;
                continue;
            }
            // Check for DEC private mode (cursor show/hide, etc.)
            const decMatch = str.slice(i).match(/^\u001b\[\?[0-9;]*[a-zA-Z]/);
            if (decMatch) {
                result += decMatch[0];
                i += decMatch[0].length;
                continue;
            }
            // Check for OSC 8 hyperlink
            const oscMatch = str.slice(i).match(/^\u001b\]8;;[^\u0007]*\u0007/);
            if (oscMatch) {
                result += oscMatch[0];
                i += oscMatch[0].length;
                continue;
            }
            /* eslint-enable no-control-regex */
        }
        // Copy visible character
        result += str[i];
        visibleIndex++;
        i++;
    }
    return result + ellipsis;
}
/**
 * Pad a string to a specific length on the right
 */
export function padRight(str, length, pad = ' ') {
    const displayWidth = getDisplayWidth(str);
    if (displayWidth >= length) {
        return str;
    }
    return str + pad.repeat(length - displayWidth);
}
/**
 * Pad a string to a specific length on the left
 */
export function padLeft(str, length, pad = ' ') {
    const displayWidth = getDisplayWidth(str);
    if (displayWidth >= length) {
        return str;
    }
    return pad.repeat(length - displayWidth) + str;
}
/**
 * Display a formatted banner with title and body content
 * Creates a bordered box around the content
 *
 * Uses Bun.stringWidth() for accurate width calculation with ANSI codes and unicode
 * Responsive to terminal width - adapts to narrow terminals
 */
export function banner(title, body, options) {
    // Get terminal width, default to 120 if not available
    const termWidth = getTerminalWidth(120);
    const border = {
        topLeft: '╭',
        topRight: '╮',
        bottomLeft: '╰',
        bottomRight: '╯',
        horizontal: '─',
        vertical: '│',
    };
    // Calculate content width first (before wrapping)
    const titleWidth = getDisplayWidth(title);
    const bodyLines = body.split('\n');
    const maxBodyWidth = Math.max(0, ...bodyLines.map((line) => getDisplayWidth(line)));
    const requiredContentWidth = Math.max(titleWidth, maxBodyWidth);
    // Box width = content + borders (2) + side spaces (2)
    const boxWidth = Math.min(requiredContentWidth + 4, termWidth);
    // If required content width exceeds terminal width, skip box and print plain text
    if (requiredContentWidth + 4 > termWidth) {
        console.log('\n' + bold(title));
        console.log(body + '\n');
        return;
    }
    // Inner width is box width minus borders (2) and side spaces (2)
    const innerWidth = boxWidth - 4;
    // Wrap text to fit box width
    const wrappedBodyLines = wrapText(body, innerWidth);
    // Colors
    const borderColor = getColor('muted');
    const titleColor = getColor('info');
    const reset = getColor('reset');
    // Build banner
    const lines = [];
    // Top border
    lines.push(`${borderColor}${border.topLeft}${border.horizontal.repeat(boxWidth - 2)}${border.topRight}${reset}`);
    if (options?.topSpacer === true || options?.topSpacer === undefined) {
        // Empty line
        lines.push(`${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`);
    }
    // Title (centered and bold)
    const titleDisplayWidth = getDisplayWidth(title);
    if (options?.centerTitle === true || options?.centerTitle === undefined) {
        const titlePadding = Math.max(0, Math.floor((innerWidth - titleDisplayWidth) / 2));
        const titleRightPadding = Math.max(0, innerWidth - titlePadding - titleDisplayWidth);
        const titleLine = ' '.repeat(titlePadding) +
            `${titleColor}${bold(title)}${reset}` +
            ' '.repeat(titleRightPadding);
        lines.push(`${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`);
    }
    else {
        const titleRightPadding = Math.max(0, innerWidth - titleDisplayWidth);
        const titleLine = `${titleColor}${bold(title)}${reset}` + ' '.repeat(titleRightPadding);
        lines.push(`${borderColor}${border.vertical} ${reset}${titleLine}${borderColor} ${border.vertical}${reset}`);
    }
    if (options?.middleSpacer === true || options?.middleSpacer === undefined) {
        // Empty line
        lines.push(`${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`);
    }
    // Body lines
    for (const line of wrappedBodyLines) {
        const lineWidth = getDisplayWidth(line);
        const linePadding = Math.max(0, innerWidth - lineWidth);
        lines.push(`${borderColor}${border.vertical} ${reset}${line}${' '.repeat(linePadding)}${borderColor} ${border.vertical}${reset}`);
    }
    if (options?.bottomSpacer === true || options?.bottomSpacer === undefined) {
        // Empty line
        lines.push(`${borderColor}${border.vertical}${' '.repeat(boxWidth - 2)}${border.vertical}${reset}`);
    }
    // Bottom border
    lines.push(`${borderColor}${border.bottomLeft}${border.horizontal.repeat(boxWidth - 2)}${border.bottomRight}${reset}`);
    // Print the banner
    console.log('\n' + lines.join('\n') + '\n');
}
/**
 * Wait for any key press before continuing
 * Displays a prompt message and waits for user input
 * Exits the process on CTRL+C
 */
export async function waitForAnyKey(message = 'Press Enter to continue...') {
    process.stdout.write(muted(message));
    // Check if we're in a TTY environment
    if (!process.stdin.isTTY) {
        // Not a TTY (CI/piped), just write newline and exit
        console.log('');
        return Promise.resolve();
    }
    // Set stdin to raw mode to read a single keypress
    process.stdin.setRawMode(true);
    process.stdin.resume();
    let rawModeSet = true;
    return new Promise((resolve) => {
        process.stdin.once('data', (data) => {
            if (rawModeSet && process.stdin.isTTY) {
                process.stdin.setRawMode(false);
                rawModeSet = false;
            }
            process.stdin.pause();
            // Check for CTRL+C (character code 3)
            if (data.length === 1 && data[0] === 3) {
                console.log('\n');
                process.exit(0);
            }
            console.log('');
            resolve();
        });
    });
}
/**
 * Prompts user with a yes/no question
 * Returns true for yes, false for no
 * Exits process if CTRL+C is pressed
 */
export async function confirm(message, defaultValue = true) {
    const suffix = defaultValue ? '[Y/n]' : '[y/N]';
    process.stdout.write(`${message} ${muted(suffix)} `);
    // Check if we're in a TTY environment
    if (!process.stdin.isTTY) {
        console.log('');
        return defaultValue;
    }
    // Set stdin to raw mode to read a single keypress
    process.stdin.setRawMode(true);
    process.stdin.resume();
    let rawModeSet = true;
    return new Promise((resolve) => {
        process.stdin.once('data', (data) => {
            if (rawModeSet && process.stdin.isTTY) {
                process.stdin.setRawMode(false);
                rawModeSet = false;
            }
            process.stdin.pause();
            // Check for CTRL+C (character code 3)
            if (data.length === 1 && data[0] === 3) {
                console.log('\n');
                process.exit(0);
            }
            const input = data.toString().trim().toLowerCase();
            console.log('');
            // Enter key (just newline) uses default
            if (input === '') {
                resolve(defaultValue);
                return;
            }
            // Check first character for y/n
            const char = input.charAt(0);
            if (char === 'y') {
                resolve(true);
            }
            else if (char === 'n') {
                resolve(false);
            }
            else {
                // Invalid input, use default
                resolve(defaultValue);
            }
        });
    });
}
/**
 * Display a signup benefits box with cyan border
 * Shows the value proposition for creating an Agentuity account
 */
export function showSignupBenefits() {
    const CYAN = Bun.color('cyan', 'ansi-16m');
    const TEXT = currentColorScheme === 'dark' ? Bun.color('white', 'ansi') : Bun.color('black', 'ansi');
    const RESET = '\x1b[0m';
    const lines = [
        '╔════════════════════════════════════════════╗',
        `║ ⨺ Signup for Agentuity             ${muted('free')}${CYAN}    ║`,
        '║                                            ║',
        `║ ✓ ${TEXT}Cloud deployment, previews and CI/CD${CYAN}     ║`,
        `║ ✓ ${TEXT}AI Gateway, KV, Vector and more${CYAN}          ║`,
        `║ ✓ ${TEXT}Observability, Tracing and Logging${CYAN}       ║`,
        `║ ✓ ${TEXT}Organization and Team support${CYAN}            ║`,
        `║ ✓ ${TEXT}And much more!${CYAN}                           ║`,
        '╚════════════════════════════════════════════╝',
    ];
    console.log('');
    lines.map((line) => console.log(CYAN + line + RESET));
    console.log('');
}
/**
 * Display a message when unauthenticated to let the user know certain capabilities are disabled
 * @param hasProfile - If true, user has logged in before so only show "Login" instead of "Sign up / Login"
 */
export function showLoggedOutMessage(appBaseUrl, hasProfile = false) {
    const YELLOW = Bun.color('yellow', 'ansi-16m');
    const TEXT = currentColorScheme === 'dark' ? Bun.color('white', 'ansi') : Bun.color('black', 'ansi');
    const RESET = '\x1b[0m';
    const signupTitle = hasProfile ? 'Login' : 'Sign up / Login';
    const signupURL = hasProfile ? `${appBaseUrl}/sign-in` : `${appBaseUrl}/sign-up`;
    const showInline = supportsHyperlinks();
    const signupLink = showInline
        ? link(signupURL, signupTitle)
        : ' '.repeat(stringWidth(signupTitle));
    // Box inner width is 46 chars, "unauthenticated. " = 17 chars
    // Padding needed: 46 - 17 - signupTitle.length - 1 (space before link) = 28 - signupTitle.length
    const paddingLength = 28 - signupTitle.length;
    const padding = ' '.repeat(paddingLength);
    // When not showing inline hyperlink, show URL on separate line with proper padding
    // Box format: "║ " + content + "║" = 48 chars total
    // Content area = 46 chars, with leading space = 45 chars for URL + padding
    const urlPadding = Math.max(0, 45 - signupURL.length);
    const showNewLine = showInline
        ? ''
        : `║ ${RESET}${link(signupURL)}${YELLOW}${' '.repeat(urlPadding)}║`;
    const lines = [
        '╔══════════════════════════════════════════════╗',
        `║ ⨺ Unauthenticated (local mode)               ║`,
        '║                                              ║',
        `║ ${TEXT}Certain capabilities such as the AI services${YELLOW} ║`,
        `║ ${TEXT}and devmode remote are unavailable when${YELLOW}      ║`,
        `║ ${TEXT}unauthenticated.${YELLOW} ${signupLink}${YELLOW}${padding}║`,
        showNewLine,
        '╚══════════════════════════════════════════════╝',
    ];
    console.log('');
    lines.filter(Boolean).map((line) => console.log(YELLOW + line + RESET));
    console.log('');
    console.log('');
}
/**
 * Display a warning when running in local-only mode (no agentuity.json project config)
 * This is shown during `agentuity dev` when the project hasn't been registered with Agentuity Cloud
 */
export function showLocalOnlyWarning() {
    const YELLOW = Bun.color('yellow', 'ansi-16m');
    const TEXT = currentColorScheme === 'dark' ? Bun.color('white', 'ansi') : Bun.color('black', 'ansi');
    const RESET = '\x1b[0m';
    const lines = [
        '╔═══════════════════════════════════════════════════════════════╗',
        `║ ⨺ Local-only mode                                             ║`,
        '║                                                               ║',
        `║ ${TEXT}This project is not registered with Agentuity Cloud.${YELLOW}          ║`,
        `║ ${TEXT}The following features are disabled:${YELLOW}                          ║`,
        `║   ${TEXT}• AI Gateway (LLM calls require provider API keys)${YELLOW}          ║`,
        `║   ${TEXT}• Public URL / Remote access${YELLOW}                                ║`,
        `║   ${TEXT}• Dashboard / Tracing / Observability${YELLOW}                       ║`,
        '║                                                               ║',
        `║ ${TEXT}To enable cloud features, create a project and login.${YELLOW}         ║`,
        `║ ${TEXT}Or set provider API keys (e.g. OPENAI_API_KEY) in .env${YELLOW}        ║`,
        '╚═══════════════════════════════════════════════════════════════╝',
    ];
    console.log('');
    lines.map((line) => console.log(YELLOW + line + RESET));
    console.log('');
}
/**
 * Copy text to clipboard
 * Returns true if successful, false otherwise
 */
export async function copyToClipboard(text) {
    try {
        const platform = process.platform;
        if (platform === 'darwin') {
            // macOS - use pbcopy
            const proc = Bun.spawn(['pbcopy'], {
                stdin: 'pipe',
            });
            proc.stdin.write(text);
            proc.stdin.end();
            await proc.exited;
            return proc.exitCode === 0;
        }
        else if (platform === 'win32') {
            // Windows - use clip
            const proc = Bun.spawn(['clip'], {
                stdin: 'pipe',
            });
            proc.stdin.write(text);
            proc.stdin.end();
            await proc.exited;
            return proc.exitCode === 0;
        }
        else {
            // Linux - try xclip first, then xsel
            try {
                const proc = Bun.spawn(['xclip', '-selection', 'clipboard'], {
                    stdin: 'pipe',
                });
                proc.stdin.write(text);
                proc.stdin.end();
                await proc.exited;
                return proc.exitCode === 0;
            }
            catch {
                // Try xsel as fallback
                const proc = Bun.spawn(['xsel', '--clipboard', '--input'], {
                    stdin: 'pipe',
                });
                proc.stdin.write(text);
                proc.stdin.end();
                await proc.exited;
                return proc.exitCode === 0;
            }
        }
    }
    catch {
        return false;
    }
}
/**
 * Extract ANSI codes from the beginning of a string
 */
function extractLeadingAnsiCodes(str) {
    // Match ANSI escape sequences at the start of the string
    // eslint-disable-next-line no-control-regex
    const match = str.match(/^(\x1b\[[0-9;]*m)+/);
    return match ? match[0] : '';
}
/**
 * Strip ANSI codes from a string
 */
function stripAnsiCodes(str) {
    // Remove all ANSI escape sequences
    /* eslint-disable no-control-regex */
    return str
        .replace(/\x1b\[[0-9;]*m/g, '') // SGR sequences
        .replace(/\x1b\[\?[0-9;]*[a-zA-Z]/g, ''); // DEC private mode
    /* eslint-enable no-control-regex */
}
/**
 * Check if a string ends with ANSI reset code
 */
function endsWithReset(str) {
    return str.endsWith('\x1b[0m') || str.endsWith(getColor('reset'));
}
/**
 * Wrap text to a maximum width
 * Handles explicit newlines and word wrapping
 * Preserves ANSI color codes across wrapped lines
 */
export function wrapText(text, maxWidth) {
    const allLines = [];
    // First split by explicit newlines
    const paragraphs = text.split('\n');
    for (const paragraph of paragraphs) {
        // Skip empty paragraphs (they become blank lines)
        if (paragraph.trim() === '') {
            allLines.push('');
            continue;
        }
        // Record starting index for this paragraph's lines
        const paragraphStart = allLines.length;
        // Extract any leading ANSI codes from the paragraph
        const leadingCodes = extractLeadingAnsiCodes(paragraph);
        const hasReset = endsWithReset(paragraph);
        // Wrap each paragraph
        const words = paragraph.split(' ');
        let currentLine = '';
        for (const word of words) {
            const testLine = currentLine ? `${currentLine} ${word}` : word;
            const testLineWidth = getDisplayWidth(testLine);
            if (testLineWidth <= maxWidth) {
                currentLine = testLine;
            }
            else {
                // If current line has content, save it
                if (currentLine) {
                    allLines.push(currentLine);
                }
                // If the word itself is longer than maxWidth, just use it as is
                // (better to have a long line than break in the middle)
                // But if we have leading codes and this isn't the first line, apply them
                if (leadingCodes && currentLine) {
                    // Strip any existing codes from the word to avoid duplication
                    const strippedWord = stripAnsiCodes(word);
                    currentLine = leadingCodes + strippedWord;
                }
                else {
                    currentLine = word;
                }
            }
        }
        if (currentLine) {
            allLines.push(currentLine);
        }
        // If the original paragraph had ANSI codes and ended with reset,
        // ensure each wrapped line ends with reset (only for this paragraph's lines)
        if (leadingCodes && hasReset) {
            for (let i = paragraphStart; i < allLines.length; i++) {
                const line = allLines[i];
                if (line !== undefined && !endsWithReset(line)) {
                    allLines[i] = line + getColor('reset');
                }
            }
        }
    }
    return allLines.length > 0 ? allLines : [''];
}
export async function spinner(messageOrOptions, callback) {
    // Normalize to options format
    let options;
    if (typeof messageOrOptions === 'string') {
        if (callback === undefined) {
            throw new Error('callback is required when first argument is a string');
        }
        options = { type: 'simple', message: messageOrOptions, callback };
    }
    else {
        options = messageOrOptions;
    }
    // assume true by default
    if (options.clearOnSuccess === undefined) {
        options.clearOnSuccess = true;
    }
    const message = options.message;
    const reset = getColor('reset');
    // Check if progress should be disabled (from global options)
    const { getOutputOptions, shouldDisableProgress } = await import('./output');
    const outputOptions = getOutputOptions();
    const noProgress = outputOptions ? shouldDisableProgress(outputOptions) : false;
    const isJsonMode = outputOptions?.json === true;
    // If stderr is not a real terminal or progress disabled, just execute
    // the callback without animation. We check stderr specifically because
    // the spinner writes ANSI sequences to stderr — isTTYLike() may return
    // true when stdout is a TTY but stderr is piped (e.g. 2>&1 in $()).
    if (!process.stderr.isTTY || noProgress) {
        try {
            const result = options.type === 'progress'
                ? await options.callback(() => { })
                : options.type === 'logger'
                    ? await options.callback((logMessage) => {
                        // In JSON mode, don't write logs to stdout
                        if (!isJsonMode) {
                            process.stdout.write(logMessage + '\n');
                        }
                    })
                    : options.type === 'countdown'
                        ? await options.callback()
                        : typeof options.callback === 'function'
                            ? await options.callback()
                            : await options.callback;
            // If clearOnSuccess is true, don't show success message
            // Also skip success message in JSON mode
            if (!options.clearOnSuccess && !isJsonMode) {
                const successColor = getColor('success');
                console.error(`${successColor}${ICONS.success} ${message}${reset}`);
            }
            return result;
        }
        catch (err) {
            const clearOnError = (options.type === 'progress' || options.type === 'simple') && options.clearOnError;
            if (!clearOnError && !isJsonMode) {
                const errorColor = getColor('error');
                console.error(`${errorColor}${ICONS.error} ${message}${reset}`);
            }
            throw err;
        }
    }
    const frames = ['◐', '◓', '◑', '◒'];
    const spinnerColors = [
        { light: '\x1b[36m', dark: '\x1b[96m' }, // cyan
        { light: '\x1b[34m', dark: '\x1b[94m' }, // blue
        { light: '\x1b[35m', dark: '\x1b[95m' }, // magenta
        { light: '\x1b[36m', dark: '\x1b[96m' }, // cyan
    ];
    const bold = '\x1b[1m';
    const cyanColor = { light: '\x1b[36m', dark: '\x1b[96m' }[currentColorScheme];
    let frameIndex = 0;
    let currentProgress;
    let remainingTime;
    const logLines = [];
    const maxLines = options.type === 'logger' ? (options.maxLines ?? 3) : 0;
    const mutedColor = getColor('muted');
    let linesRendered = 0;
    // Get terminal width for truncation
    const termWidth = getTerminalWidth(80);
    const maxLineWidth = Math.min(80, termWidth);
    // Function to render spinner with optional log lines
    const renderSpinner = () => {
        // Move cursor up to start of our output area if we've rendered before
        if (linesRendered > 0) {
            process.stderr.write(`\x1b[${linesRendered}A`);
        }
        const colorDef = spinnerColors[frameIndex % spinnerColors.length] ?? spinnerColors[0];
        const color = colorDef?.[currentColorScheme] ?? '';
        const frame = `${color}${bold}${frames[frameIndex % frames.length] ?? ''}${reset}`;
        // Add progress indicator or countdown timer if available
        let indicator = '';
        if (currentProgress !== undefined) {
            indicator = ` ${cyanColor}${Math.floor(currentProgress)}%${reset}`;
        }
        else if (remainingTime !== undefined) {
            const minutes = Math.floor(remainingTime / 60);
            const seconds = Math.floor(remainingTime % 60);
            const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
            indicator = ` ${mutedColor}(${timeStr} remaining)${reset}`;
        }
        // Render spinner line
        process.stderr.write(`\r\x1b[K${frame} ${message}${indicator}\n`);
        // Render log lines if in logger mode
        if (options.type === 'logger') {
            const displayLines = maxLines < 0 ? logLines : logLines.slice(-maxLines);
            for (const line of displayLines) {
                const displayLine = getDisplayWidth(line) > maxLineWidth ? truncateToWidth(line, maxLineWidth) : line;
                process.stderr.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
            }
            linesRendered = 1 + displayLines.length;
        }
        else {
            linesRendered = 1;
        }
        frameIndex++;
    };
    // Save cursor position and hide cursor
    process.stderr.write('\x1B[s\x1B[?25l');
    // Initial render
    renderSpinner();
    // Start animation
    const interval = setInterval(renderSpinner, 120);
    // Progress callback
    const progressCallback = (progress) => {
        currentProgress = Math.min(100, Math.max(0, progress));
    };
    // Log callback
    const logCallback = (logMessage) => {
        logLines.push(logMessage);
    };
    // Countdown interval tracking
    let countdownInterval;
    let keypressListener;
    // Helper to clean up all resources
    const cleanup = () => {
        if (countdownInterval) {
            clearInterval(countdownInterval);
        }
        if (keypressListener) {
            process.stdin.off('data', keypressListener);
            if (process.stdin.isTTY) {
                process.stdin.setRawMode(false);
                process.stdin.pause();
            }
        }
        process.off('SIGINT', cleanupAndExit);
    };
    // Set up SIGINT handler for clean exit
    const cleanupAndExit = () => {
        cleanup();
        // Stop animation
        clearInterval(interval);
        // Move cursor to start of output, clear only our lines (not to end of screen)
        if (linesRendered > 0) {
            process.stderr.write(`\x1b[${linesRendered}A`);
            for (let i = 0; i < linesRendered; i++) {
                process.stderr.write('\x1b[2K'); // Clear entire line
                if (i < linesRendered - 1) {
                    process.stderr.write('\x1b[B'); // Move down one line
                }
            }
            process.stderr.write(`\x1b[${linesRendered}A\r`); // Move back up
        }
        process.stderr.write('\x1B[?25h'); // Show cursor
        process.exit(130); // Standard exit code for SIGINT
    };
    process.on('SIGINT', cleanupAndExit);
    try {
        // For countdown, set up timer tracking and optional keyboard listener
        if (options.type === 'countdown') {
            const startTime = Date.now();
            remainingTime = options.timeoutMs / 1000;
            countdownInterval = setInterval(() => {
                const elapsed = Date.now() - startTime;
                remainingTime = Math.max(0, (options.timeoutMs - elapsed) / 1000);
            }, 100);
            // Set up Enter key listener if callback provided
            if (options.onEnterPress && process.stdin.isTTY) {
                process.stdin.setRawMode(true);
                process.stdin.resume();
                keypressListener = (chunk) => {
                    const key = chunk.toString();
                    // Check for Enter key (both \r and \n)
                    if (key === '\r' || key === '\n') {
                        options.onEnterPress();
                    }
                    // Check for Ctrl+C - let it propagate as SIGINT
                    if (key === '\x03') {
                        process.kill(process.pid, 'SIGINT');
                    }
                };
                process.stdin.on('data', keypressListener);
            }
        }
        // Execute callback
        const result = options.type === 'countdown'
            ? await options.callback()
            : options.type === 'progress'
                ? await options.callback(progressCallback)
                : options.type === 'logger'
                    ? await options.callback(logCallback)
                    : typeof options.callback === 'function'
                        ? await options.callback()
                        : await options.callback;
        cleanup();
        // Stop animation first
        clearInterval(interval);
        // Move cursor to start of output, clear only our lines (not to end of screen)
        if (linesRendered > 0) {
            process.stderr.write(`\x1b[${linesRendered}A`);
            for (let i = 0; i < linesRendered; i++) {
                process.stderr.write('\r\x1b[2K'); // Clear entire line
                if (i < linesRendered - 1) {
                    process.stderr.write('\x1b[B'); // Move down one line
                }
            }
            // After loop, cursor is at last cleared line (linesRendered - 1 from start)
            // Move up (linesRendered - 1) to get back to start position
            if (linesRendered > 1) {
                process.stderr.write(`\x1b[${linesRendered - 1}A`);
            }
            process.stderr.write('\r');
        }
        process.stderr.write('\x1B[?25h'); // Show cursor
        // If clearOnSuccess is false, show success message
        if (!options.clearOnSuccess) {
            // Show success
            const successColor = getColor('success');
            console.error(`${successColor}${ICONS.success} ${message}${reset}`);
        }
        return result;
    }
    catch (err) {
        cleanup();
        // Stop animation first
        clearInterval(interval);
        // Move cursor to start of output, clear only our lines (not to end of screen)
        if (linesRendered > 0) {
            process.stderr.write(`\x1b[${linesRendered}A`);
            for (let i = 0; i < linesRendered; i++) {
                process.stderr.write('\r\x1b[2K'); // Clear entire line
                if (i < linesRendered - 1) {
                    process.stderr.write('\x1b[B'); // Move down one line
                }
            }
            // After loop, cursor is at last cleared line (linesRendered - 1 from start)
            // Move up (linesRendered - 1) to get back to start position
            if (linesRendered > 1) {
                process.stderr.write(`\x1b[${linesRendered - 1}A`);
            }
            process.stderr.write('\r');
        }
        process.stderr.write('\x1B[?25h'); // Show cursor
        // Show error (unless clearOnError is set for custom error handling)
        const clearOnError = (options.type === 'progress' || options.type === 'simple') && options.clearOnError;
        if (!clearOnError) {
            const errorColor = getColor('error');
            const errorMessage = err instanceof Error ? err.message : String(err);
            console.error(`${errorColor}${ICONS.error} ${message}: ${errorMessage}${reset}`);
        }
        throw err;
    }
}
/**
 * Alias for spinner function (for better semantics when using progress/logger types)
 */
export const progress = spinner;
/**
 * Run an external command and stream its output with a live UI
 *
 * Displays the command with a colored $ prompt:
 * - Blue while running
 * - Green on successful exit (code 0)
 * - Red on failed exit (code != 0)
 *
 * Shows the last 3 lines of output as it streams.
 */
export async function runCommand(options) {
    const { command, cmd, cwd, env, clearOnSuccess = false, truncate = true, maxLinesOutput = 3, maxLinesOnFailure = 10, } = options;
    const isTTY = process.stdout.isTTY;
    // If not a TTY, just run the command normally and log output
    if (!isTTY) {
        const proc = Bun.spawn(cmd, {
            cwd,
            env: { ...process.env, ...env },
            stdout: 'inherit',
            stderr: 'inherit',
        });
        return await proc.exited;
    }
    // Colors using Bun.color
    const blue = currentColorScheme === 'light'
        ? Bun.color('#0000FF', 'ansi') || '\x1b[34m'
        : Bun.color('#5C9CFF', 'ansi') || '\x1b[94m';
    const green = getColor('success');
    const red = getColor('error');
    const cmdColor = currentColorScheme === 'light'
        ? '\x1b[1m' + (Bun.color('#00008B', 'ansi') || '\x1b[34m')
        : Bun.color('#FFFFFF', 'ansi') || '\x1b[97m'; // bold dark blue / white
    const mutedColor = Bun.color('#808080', 'ansi') || '\x1b[90m';
    const reset = getColor('reset');
    // Get terminal width
    const termWidth = getTerminalWidth(80);
    const maxCmdWidth = Math.min(40, termWidth);
    const maxLineWidth = Math.min(80, termWidth);
    // Truncate command if needed
    const displayCmd = getDisplayWidth(command) > maxCmdWidth ? truncateToWidth(command, maxCmdWidth) : command;
    // Store all output lines, display subset based on context
    const allOutputLines = [];
    let linesRendered = 0;
    // Hide cursor
    process.stdout.write('\x1B[?25l');
    // Render the command and output lines in place
    const renderOutput = (linesToShow) => {
        // Move cursor up to start of our output area
        if (linesRendered > 0) {
            process.stdout.write(`\x1b[${linesRendered}A`);
        }
        // Render command line
        process.stdout.write(`\r\x1b[K${blue}$${reset} ${cmdColor}${displayCmd}${reset}\n`);
        // Get last N lines to display
        const displayLines = allOutputLines.slice(-linesToShow);
        // Render output lines
        for (const line of displayLines) {
            const displayLine = getDisplayWidth(line) > maxLineWidth ? truncateToWidth(line, maxLineWidth) : line;
            process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`);
        }
        // Update count of lines we've rendered (command + output lines)
        linesRendered = 1 + displayLines.length;
    };
    // Initial display
    renderOutput(maxLinesOutput);
    try {
        // Spawn the command
        const proc = Bun.spawn(cmd, {
            cwd,
            env: { ...process.env, ...env },
            stdout: 'pipe',
            stderr: 'pipe',
        });
        // Process output streams
        const processStream = async (stream) => {
            const reader = stream.getReader();
            const decoder = new TextDecoder();
            let buffer = '';
            try {
                while (true) {
                    const { done, value } = await reader.read();
                    if (done)
                        break;
                    buffer += decoder.decode(value, { stream: true });
                    const lines = buffer.split('\n');
                    buffer = lines.pop() || ''; // Keep incomplete line in buffer
                    for (const line of lines) {
                        if (line.trim()) {
                            // Strip ANSI codes from command output to prevent cursor/display issues
                            allOutputLines.push(stripAnsi(line));
                            renderOutput(maxLinesOutput); // Show last N lines while streaming
                        }
                    }
                }
            }
            finally {
                reader.releaseLock();
            }
        };
        // Process both stdout and stderr
        await Promise.all([processStream(proc.stdout), processStream(proc.stderr)]);
        // Wait for process to exit
        const exitCode = await proc.exited;
        // If clearOnSuccess is true and command succeeded, clear everything
        if (clearOnSuccess && exitCode === 0) {
            if (linesRendered > 0) {
                // Move up to the command line
                process.stdout.write(`\x1b[${linesRendered}A`);
                // Clear each line (entire line)
                for (let i = 0; i < linesRendered; i++) {
                    process.stdout.write('\r\x1b[2K'); // Clear entire line
                    if (i < linesRendered - 1) {
                        process.stdout.write('\x1b[B'); // Move down one line
                    }
                }
                // After loop, cursor is at last cleared line (linesRendered - 1 from start)
                // Move up (linesRendered - 1) to get back to start position
                if (linesRendered > 1) {
                    process.stdout.write(`\x1b[${linesRendered - 1}A`);
                }
                process.stdout.write('\r');
            }
            return exitCode;
        }
        // Determine how many lines to show in final output
        const finalLinesToShow = exitCode === 0 ? maxLinesOutput : maxLinesOnFailure;
        const finalOutputLines = allOutputLines.slice(-finalLinesToShow);
        // Clear all rendered lines completely (only our lines, not previous output)
        if (linesRendered > 0) {
            // Move up to the command line (first line of our output)
            process.stdout.write(`\x1b[${linesRendered}A`);
            // Clear the lines we rendered during streaming
            for (let i = 0; i < linesRendered; i++) {
                process.stdout.write('\r\x1b[2K'); // Clear entire line
                if (i < linesRendered - 1) {
                    process.stdout.write('\x1b[B'); // Move down one line
                }
            }
            // After loop, cursor is at last cleared line (linesRendered - 1 from start)
            // Move up (linesRendered - 1) to get back to start position
            if (linesRendered > 1) {
                process.stdout.write(`\x1b[${linesRendered - 1}A`);
            }
            process.stdout.write('\r');
        }
        // Determine icon based on exit code
        const icon = exitCode === 0 ? ICONS.success : ICONS.error;
        const statusColor = exitCode === 0 ? green : red;
        // Show final status: icon + command
        process.stdout.write(`\r\x1b[K${statusColor}${icon}${reset} ${cmdColor}${displayCmd}${reset}\n`);
        // Show final output lines (clearing each line first in case we're using more lines than before)
        for (const line of finalOutputLines) {
            const displayLine = truncate && getDisplayWidth(line) > maxLineWidth
                ? truncateToWidth(line, maxLineWidth)
                : line;
            process.stdout.write(`\x1b[2K${mutedColor}${displayLine}${reset}\n`);
        }
        // If we're showing more lines than we had before, the extra lines may contain old content
        // We've already written over them, so they're clean now
        return exitCode;
    }
    catch (err) {
        // Move cursor up to clear our UI
        if (linesRendered > 0) {
            process.stdout.write(`\x1b[${linesRendered}A`);
            // Clear all our lines
            for (let i = 0; i < linesRendered; i++) {
                process.stdout.write('\r\x1b[K\n');
            }
            process.stdout.write(`\x1b[${linesRendered}A`);
        }
        // Show error status
        process.stdout.write(`\r\x1b[K${red}$${reset} ${cmdColor}${displayCmd}${reset}\n`);
        // Log the error
        const errorMsg = err instanceof Error ? err.message : String(err);
        console.error(`${red}${ICONS.error} Failed to spawn command: ${errorMsg}${reset}`);
        if (cwd) {
            console.error(`${mutedColor}  cwd: ${cwd}${reset}`);
        }
        console.error(`${mutedColor}  cmd: ${cmd.join(' ')}${reset}`);
        return 1; // Return non-zero exit code
    }
    finally {
        // Always restore cursor visibility
        process.stdout.write('\x1B[?25h');
    }
}
/**
 * Prompt user for text input
 * Returns the input string
 */
export async function prompt(message) {
    process.stdout.write(message);
    // Check if we're in a TTY environment
    if (!process.stdin.isTTY) {
        console.log('');
        return '';
    }
    // Use readline for full line input
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });
    return new Promise((resolve) => {
        rl.question('', (answer) => {
            rl.close();
            resolve(answer);
        });
    });
}
/**
 * Select an organization from a list.
 *
 * @param orgs - List of organizations to choose from
 * @param initial - Preferred org ID to pre-select (from saved preferences)
 * @param autoSelect - If true, auto-select preferred org without prompting (for --confirm or non-interactive)
 * @returns The selected organization ID
 */
export async function selectOrganization(orgs, initial, autoSelect) {
    if (orgs.length === 0) {
        fatal('You do not belong to any organizations.\n' +
            'Please contact support or create an organization at https://agentuity.com');
    }
    // 1. Environment variable always takes precedence
    if (process.env.AGENTUITY_CLOUD_ORG_ID) {
        const org = orgs.find((o) => o.id === process.env.AGENTUITY_CLOUD_ORG_ID);
        if (org) {
            return org.id;
        }
    }
    // 2. Auto-select if only one org (regardless of TTY mode or autoSelect)
    if (orgs.length === 1 && orgs[0]) {
        return orgs[0].id;
    }
    // 3. Auto-select mode (--confirm flag or explicit autoSelect)
    // Use preferred org if set, otherwise fall back to first org
    if (autoSelect) {
        if (initial) {
            const initialOrg = orgs.find((o) => o.id === initial);
            if (initialOrg) {
                return initialOrg.id;
            }
        }
        // Fall back to first org with warning
        const firstOrg = orgs[0];
        if (firstOrg) {
            warning(`Multiple organizations found. Auto-selecting first org: ${firstOrg.name}. ` +
                `Set AGENTUITY_CLOUD_ORG_ID, use --org-id, or run 'agentuity auth org select' to set a default.`);
            return firstOrg.id;
        }
    }
    // 4. Check for non-interactive environment (check both stdin and stdout)
    const isNonInteractive = !process.stdin.isTTY || !process.stdout.isTTY;
    if (isNonInteractive) {
        // In non-interactive mode, use preferred org if set
        if (initial) {
            const initialOrg = orgs.find((o) => o.id === initial);
            if (initialOrg) {
                return initialOrg.id;
            }
        }
        // Fall back to first org with warning
        const firstOrg = orgs[0];
        if (firstOrg) {
            warning(`Multiple organizations found. Auto-selecting first org: ${firstOrg.name}. ` +
                `Set AGENTUITY_CLOUD_ORG_ID, use --org-id, or run 'agentuity auth org select' to set a default.`);
            return firstOrg.id;
        }
    }
    // 5. Interactive mode - show selector with preferred org pre-selected
    const initialIndex = initial ? orgs.findIndex((o) => o.id === initial) : 0;
    const response = await enquirer.prompt({
        type: 'select',
        name: 'action',
        message: 'Select an organization',
        initial: initialIndex >= 0 ? initialIndex : 0,
        choices: orgs.map((o) => ({ message: o.name, name: o.id })),
    });
    return response.action;
}
/**
 * show a project list picker
 *
 * @param apiClient
 * @param showDeployment
 * @returns
 */
export async function showProjectList(apiClient, showDeploymentId = false) {
    const projects = await spinner({
        message: 'Fetching projects',
        clearOnSuccess: true,
        callback: () => {
            return projectList(apiClient, showDeploymentId);
        },
    });
    if (projects.length === 0) {
        return '';
    }
    // TODO: might want to sort by the last org_id we used
    if (projects) {
        projects.sort((a, b) => {
            return a.name.localeCompare(b.name);
        });
    }
    const response = await enquirer.prompt({
        type: 'select',
        name: 'id',
        message: 'Select a project:',
        choices: projects.map((p) => ({
            name: p.id,
            message: `${p.name.padEnd(25, ' ')} ${muted(p.id)} ${showDeploymentId ? muted(p.latestDeploymentId ?? 'no deployment') : ''}`,
        })),
    });
    return response.id;
}
/**
 * Show a profile list picker
 *
 * @param profiles List of profiles to choose from
 * @param message Prompt message
 * @returns The name of the selected profile
 */
export async function showProfileList(profiles, message = 'Select a profile:') {
    if (profiles.length === 0) {
        warning('No profiles found');
        process.exit(0);
    }
    // If only one profile, just return it? No, let them confirm/see it if they asked to pick?
    // But for "use" it implies switching. If only one, you are already on it or it's the only choice.
    // But for delete, you might want to delete the only one.
    // So always show list.
    // Find currently selected profile for initial selection
    const selectedProfile = profiles.find((p) => p.selected);
    const initial = selectedProfile ? selectedProfile.name : undefined;
    // If non-interactive, return initial or first
    if (!process.stdin.isTTY) {
        if (initial)
            return initial;
        const firstProfile = profiles[0];
        if (profiles.length === 1 && firstProfile) {
            return firstProfile.name;
        }
        fatal('Profile selection required but cannot prompt in non-interactive environment. ' +
            'Pass a profile name explicitly when running non-interactively.');
    }
    const response = await enquirer.prompt({
        type: 'select',
        name: 'name',
        message: message,
        initial: initial,
        choices: profiles.map((p) => ({
            name: p.name,
            message: p.selected ? `${p.name.padEnd(15, ' ')} ${muted('(current)')}` : p.name,
        })),
    });
    return response.name;
}
export function json(value) {
    const stringValue = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
    if (shouldUseColors() && process.stdout.isTTY) {
        try {
            console.log(colorize(stringValue));
            return;
        }
        catch {
            /* */
        }
    }
    console.log(stringValue);
}
export function plural(count, singular, plural) {
    switch (count) {
        case 0:
            return plural;
        case 1:
            return singular;
        default:
            return plural;
    }
}
/**
 * Calculate the minimum width needed to display a horizontal table
 * Accounts for column padding, borders, and content width
 */
function calculateTableWidth(data, columnNames, padStart = '') {
    const columnWidths = columnNames.map((colName) => {
        let maxWidth = getDisplayWidth(padStart + colName);
        for (const row of data) {
            const value = row[colName];
            const valueStr = value !== undefined && value !== null ? String(value) : '';
            const valueWidth = getDisplayWidth(valueStr);
            if (valueWidth > maxWidth) {
                maxWidth = valueWidth;
            }
        }
        return maxWidth;
    });
    // Add padding (1 space each side) and border characters per column
    // cli-table3 uses: │ col1 │ col2 │ = 3 chars per column + 1 for final border
    const paddingPerColumn = 3;
    const totalWidth = columnWidths.reduce((sum, w) => sum + w + paddingPerColumn, 0) + 1;
    return totalWidth;
}
/**
 * Render table in vertical (stacked) format for narrow terminals
 */
function renderVerticalTable(data, columnNames, padStart = '') {
    const lines = [];
    const mutedColor = getColor('muted');
    const reset = getColor('reset');
    // Calculate max column name width for alignment
    const maxLabelWidth = Math.max(...columnNames.map((name) => 1 + getDisplayWidth(padStart + name)));
    for (let i = 0; i < data.length; i++) {
        const row = data[i];
        if (!row)
            continue;
        for (const colName of columnNames) {
            const value = row[colName];
            const valueStr = value !== undefined && value !== null ? String(value) : '';
            const paddedLabel = `${padStart}${colName}:`.padEnd(maxLabelWidth);
            lines.push(`${mutedColor}${paddedLabel}${reset}  ${valueStr}`);
        }
        // Add empty line between rows (but not after last row)
        if (i < data.length - 1) {
            lines.push('');
        }
    }
    return lines.join('\n') + '\n';
}
/**
 * Display data in a formatted table using cli-table3
 *
 * Supports two modes:
 * 1. Simple mode: Pass data array and optional column names
 * 2. Advanced mode: Pass column configurations with custom names and alignment
 *
 * Automatically switches between horizontal (wide) and vertical (narrow) layouts
 * based on terminal width. Use the `layout` option to force a specific mode.
 *
 * @param data - Array of data objects to display
 * @param columns - Column names or column configurations
 * @param options - Additional options
 * @returns If render=true, returns the table as a string, otherwise prints to stdout
 */
export function table(data, columns, options) {
    if (!data || data.length === 0) {
        return options?.render ? '' : undefined;
    }
    // Determine if we're using advanced column config or simple column names
    const isAdvancedMode = columns && columns.length > 0 && typeof columns[0] === 'object';
    let columnNames;
    let colAligns;
    if (isAdvancedMode) {
        // Advanced mode: use provided column configurations
        const columnConfigs = columns;
        columnNames = columnConfigs.map((col) => col.name);
        colAligns = columnConfigs.map((col) => col.alignment || 'left');
    }
    else {
        // Simple mode: determine column names from data or columns parameter
        const firstRow = data[0];
        columnNames = columns
            ? columns.map((c) => String(c))
            : data.length > 0 && firstRow
                ? Object.keys(firstRow)
                : [];
        colAligns = columnNames.map(() => 'left');
    }
    // Determine layout mode
    const layout = options?.layout ?? 'auto';
    const termWidth = getTerminalWidth(80);
    const tableWidth = calculateTableWidth(data, columnNames, options?.padStart);
    const useVertical = layout === 'vertical' || (layout === 'auto' && tableWidth > termWidth);
    let output;
    if (useVertical) {
        output = renderVerticalTable(data, columnNames, options?.padStart);
    }
    else {
        // eslint-disable-next-line @typescript-eslint/no-require-imports
        const Table = require('cli-table3');
        const headings = columnNames.map((name) => heading(name));
        const t = new Table({
            head: headings,
            colAligns,
            wordWrap: true,
            style: {
                head: [], // Disable cli-table3's default red styling - we apply our own via heading()
                border: [], // Disable default border styling too
            },
            colors: false, // Completely disable cli-table3's color system to preserve our ANSI codes
        });
        // Add rows to table
        for (const row of data) {
            const rowData = [];
            for (const colName of columnNames) {
                const value = row[colName];
                rowData.push(value !== undefined && value !== null ? String(value) : '');
            }
            t.push(rowData);
        }
        output = t.toString();
    }
    if (options?.render) {
        return output;
    }
    else {
        console.log(output);
    }
}
export function formatBytes(bytes) {
    if (bytes === 0)
        return '0 B';
    if (bytes < 1024)
        return `${bytes} B`;
    if (bytes < 1024 * 1024)
        return `${(bytes / 1024).toFixed(2)} KB`;
    if (bytes < 1024 * 1024 * 1024)
        return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
    if (bytes < 1024 * 1024 * 1024 * 1024)
        return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
    return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TB`;
}
export function clearLastLines(n, s) {
    const x = s ?? ((v) => process.stdout.write(v));
    for (let i = 0; i < n; i++) {
        x('\x1b[2K'); // clear line
        x('\x1b[0G'); // cursor to col 0
        if (i < n - 1) {
            x('\x1b[1A'); // move up
        }
    }
}
//# sourceMappingURL=tui.js.map