/**
 * Interactive prompt system for TUI
 */
import * as readline from 'node:readline';
import { symbols } from './symbols';
import { colors } from './colors';
/**
 * Main prompt flow class
 */
export class PromptFlow {
    states = [];
    isInteractive() {
        return Boolean(process.stdin.isTTY && process.stdout.isTTY);
    }
    nonInteractiveError(message) {
        return new Error(`${message} Use the appropriate --flag or environment variable to provide input.`);
    }
    /**
     * Display intro banner
     */
    intro(title) {
        console.log(`${colors.secondary(symbols.squareTL)}   ${colors.inverseCyan(` ⨺ ${title} `)}`);
        console.log(colors.secondary(symbols.bar));
    }
    /**
     * Display outro message
     */
    outro(...messages) {
        for (const message of messages) {
            console.log(colors.secondary(symbols.bar));
            console.log(`${colors.secondary(symbols.squareBL)}  ${message}`);
        }
        console.log();
        // Ensure stdin is properly closed
        if (process.stdin.isTTY) {
            process.stdin.pause();
            process.stdin.setRawMode(false);
        }
    }
    /**
     * Text input prompt
     */
    async text(options) {
        const { message, validate } = options;
        const initial = options.initial ?? '';
        const hasDefault = options.initial !== undefined;
        if (!this.isInteractive()) {
            if (hasDefault) {
                const validationResult = validate ? await validate(initial) : true;
                if (validationResult === true) {
                    return initial;
                }
                // Validation failed - include the error message if it's a string
                const errorDetail = typeof validationResult === 'string' ? `: ${validationResult}` : '';
                throw this.nonInteractiveError(`Cannot prompt for "${message}" in non-interactive mode. Validation failed for default value "${initial}"${errorDetail}.`);
            }
            throw this.nonInteractiveError(`Cannot prompt for "${message}" in non-interactive mode.`);
        }
        return new Promise((resolve, reject) => {
            const rl = readline.createInterface({
                input: process.stdin,
                output: process.stdout,
                prompt: `${colors.secondary(symbols.bar)}  `, // custom prompt instead of default ">"
            });
            let hasError = false;
            let hadValidationError = false;
            const showPrompt = () => {
                // Show prompt with active symbol
                process.stdout.write(`${colors.active(symbols.active)}  ${message}\n`);
                if (options.hint) {
                    process.stdout.write(`${colors.secondary(symbols.bar)}  ${colors.muted(options.hint)}\n`);
                }
                // Use readline's prompt for the input line
                rl.prompt();
            };
            showPrompt();
            rl.on('line', async (input) => {
                const trimmed = input.trim();
                // After a validation error, require explicit input - don't fall back to initial
                const value = trimmed.length > 0 ? trimmed : hadValidationError ? '' : initial;
                // Validate
                if (validate) {
                    try {
                        const result = await validate(value);
                        if (result !== true) {
                            const errorMsg = typeof result === 'string' ? result : 'Invalid input';
                            // Clear all previous lines (prompt + optional error)
                            const linesToClear = hasError ? 3 : 2;
                            readline.moveCursor(process.stdout, 0, -linesToClear);
                            readline.clearScreenDown(process.stdout);
                            // Redraw prompt with error
                            process.stdout.write(`${colors.error(symbols.error)}  ${message}\n${colors.secondary(symbols.bar)}  ${colors.error(errorMsg)}\n`);
                            // Use readline's prompt for the input line
                            rl.prompt();
                            hasError = true;
                            hadValidationError = true;
                            return;
                        }
                    }
                    catch (error) {
                        // Handle validation errors
                        const errorMsg = error instanceof Error ? error.message : 'Validation failed';
                        // Clear all previous lines
                        const linesToClear = hasError ? 3 : 2;
                        readline.moveCursor(process.stdout, 0, -linesToClear);
                        readline.clearScreenDown(process.stdout);
                        // Show error and cleanup
                        process.stdout.write(`${colors.error(symbols.error)}  ${message}\n${colors.secondary(symbols.bar)}  ${colors.error(errorMsg)}\n`);
                        rl.close();
                        if (process.stdin.isTTY) {
                            process.stdin.pause();
                        }
                        reject(error);
                        return;
                    }
                }
                // Clear all lines and show completed state
                const linesToClear = hasError ? 4 : 3;
                readline.moveCursor(process.stdout, 0, -linesToClear);
                readline.clearScreenDown(process.stdout);
                // If value is empty, only show message and separator (no value line)
                if (value === '') {
                    process.stdout.write(`${colors.completed(symbols.completed)}  ${message}\n${colors.secondary(symbols.bar)}\n`);
                }
                else {
                    process.stdout.write(`${colors.completed(symbols.completed)}  ${message}\n${colors.secondary(symbols.bar)}  ${colors.muted(value)}\n${colors.secondary(symbols.bar)}\n`);
                }
                this.states.push({
                    type: 'completed',
                    message,
                    value,
                });
                rl.close();
                // Important: pause stdin so next prompt can use it
                if (process.stdin.isTTY) {
                    process.stdin.pause();
                }
                resolve(value);
            });
            rl.on('SIGINT', () => {
                rl.close();
                console.log('\n');
                this.cancel('Operation cancelled');
                reject(new Error('User cancelled'));
            });
        });
    }
    /**
     * Confirm (yes/no) prompt
     */
    async confirm(options) {
        const { message, initial = false } = options;
        if (!this.isInteractive()) {
            return initial;
        }
        return new Promise((resolve, reject) => {
            const hint = initial ? 'Y/n' : 'y/N';
            // Resume stdin if it was paused
            if (process.stdin.isTTY && process.stdin.isPaused()) {
                process.stdin.resume();
            }
            // Hide cursor
            if (process.stdout.isTTY) {
                process.stdout.write('\x1b[?25l');
            }
            process.stdout.write(`${colors.active(symbols.active)}  ${message} ${colors.muted(`(${hint})`)} `);
            readline.emitKeypressEvents(process.stdin);
            if (process.stdin.isTTY) {
                process.stdin.setRawMode(true);
            }
            const onKeypress = (str, key) => {
                const normalized = (str || '').toLowerCase();
                let value = initial;
                // Check for y/n keypress
                if (normalized === 'y') {
                    value = true;
                }
                else if (normalized === 'n') {
                    value = false;
                }
                else if (key.name === 'return') {
                    // Enter key uses default
                    value = initial;
                }
                else if (key.ctrl && key.name === 'c') {
                    cleanup();
                    console.log('\n');
                    this.cancel('Operation cancelled');
                    reject(new Error('User cancelled'));
                    return;
                }
                else {
                    // Ignore other keys
                    return;
                }
                cleanup();
                // Clear the line and show completed state
                readline.cursorTo(process.stdout, 0);
                readline.clearLine(process.stdout, 0);
                const displayValue = value ? 'Yes' : 'No';
                process.stdout.write(`${colors.completed(symbols.completed)}  ${message}\n${colors.secondary(symbols.bar)}  ${colors.muted(displayValue)}\n${colors.secondary(symbols.bar)}\n`);
                this.states.push({
                    type: 'completed',
                    message,
                    value,
                });
                resolve(value);
            };
            const cleanup = () => {
                process.stdin.removeListener('keypress', onKeypress);
                if (process.stdin.isTTY) {
                    process.stdin.setRawMode(false);
                    // Show cursor again
                    process.stdout.write('\x1b[?25h');
                }
            };
            process.stdin.on('keypress', onKeypress);
        });
    }
    /**
     * Select (single choice) prompt
     */
    async select(options) {
        const { message, options: choices, initial } = options;
        if (!this.isInteractive()) {
            let selectedIndex = choices.findIndex((c) => c.value === initial);
            if (selectedIndex === -1)
                selectedIndex = 0;
            const selected = choices[selectedIndex];
            if (!selected) {
                throw this.nonInteractiveError(`Cannot prompt for "${message}" in non-interactive mode.`);
            }
            return selected.value;
        }
        return new Promise((resolve, reject) => {
            let selectedIndex = choices.findIndex((c) => c.value === initial);
            if (selectedIndex === -1)
                selectedIndex = 0;
            let hasRendered = false;
            const render = () => {
                // Clear previous render
                if (hasRendered && process.stdout.isTTY) {
                    // Move cursor up to the start of the prompt (message + all choices)
                    const totalLines = choices.length + 1;
                    readline.moveCursor(process.stdout, 0, -totalLines);
                    readline.cursorTo(process.stdout, 0);
                    readline.clearScreenDown(process.stdout);
                }
                process.stdout.write(`${colors.active(symbols.active)}  ${message}\n`);
                choices.forEach((choice, index) => {
                    const isSelected = index === selectedIndex;
                    const symbol = isSelected ? symbols.radioActive : symbols.radioInactive;
                    const colorFn = isSelected ? colors.active : colors.secondary;
                    const label = choice.hint
                        ? `${choice.label} ${colors.muted(choice.hint)}`
                        : choice.label;
                    process.stdout.write(`${colors.secondary(symbols.bar)}  ${colorFn(symbol)}  ${label}\n`);
                });
                hasRendered = true;
            };
            // Resume stdin if it was paused
            if (process.stdin.isTTY && process.stdin.isPaused()) {
                process.stdin.resume();
            }
            render();
            readline.emitKeypressEvents(process.stdin);
            if (process.stdin.isTTY) {
                process.stdin.setRawMode(true);
            }
            const onKeypress = (_str, key) => {
                if (key.name === 'up' || key.name === 'k') {
                    selectedIndex = Math.max(0, selectedIndex - 1);
                    render();
                }
                else if (key.name === 'down' || key.name === 'j') {
                    selectedIndex = Math.min(choices.length - 1, selectedIndex + 1);
                    render();
                }
                else if (key.name === 'return') {
                    cleanup();
                    const selected = choices[selectedIndex];
                    if (!selected) {
                        reject(new Error('No selection available'));
                        return;
                    }
                    // Clear all lines (message + all choices)
                    const totalLines = choices.length + 1;
                    for (let i = 0; i < totalLines; i++) {
                        readline.moveCursor(process.stdout, 0, -1);
                        readline.clearLine(process.stdout, 0);
                    }
                    readline.cursorTo(process.stdout, 0);
                    // Show completed state
                    process.stdout.write(`${colors.completed(symbols.completed)}  ${message}\n${colors.secondary(symbols.bar)}  ${colors.muted(selected.label)}\n${colors.secondary(symbols.bar)}\n`);
                    this.states.push({
                        type: 'completed',
                        message,
                        value: selected.value,
                    });
                    resolve(selected.value);
                }
                else if (key.ctrl && key.name === 'c') {
                    cleanup();
                    console.log('\n');
                    this.cancel('Operation cancelled');
                    reject(new Error('User cancelled'));
                }
            };
            const cleanup = () => {
                process.stdin.removeListener('keypress', onKeypress);
                if (process.stdin.isTTY) {
                    process.stdin.setRawMode(false);
                }
            };
            process.stdin.on('keypress', onKeypress);
        });
    }
    /**
     * Multi-select (multiple choices) prompt
     */
    async multiselect(options) {
        const { message, options: choices, initial = [] } = options;
        if (!this.isInteractive()) {
            const choiceValues = new Set(choices.map((choice) => choice.value));
            return initial.filter((value) => choiceValues.has(value));
        }
        return new Promise((resolve, reject) => {
            let cursorIndex = 0;
            const selected = new Set(choices.map((c, i) => (initial.includes(c.value) ? i : -1)).filter((i) => i >= 0));
            let hasRendered = false;
            const render = () => {
                // Clear previous render
                if (hasRendered && process.stdout.isTTY) {
                    // Move cursor up to the start of the prompt (message + all choices)
                    const totalLines = choices.length + 1;
                    readline.moveCursor(process.stdout, 0, -totalLines);
                    readline.cursorTo(process.stdout, 0);
                    readline.clearScreenDown(process.stdout);
                }
                process.stdout.write(`${colors.active(symbols.active)}  ${message} ${colors.muted('(space to select, enter to confirm)')}\n`);
                choices.forEach((choice, index) => {
                    const isCursor = index === cursorIndex;
                    const isSelected = selected.has(index);
                    const symbol = isSelected ? symbols.checkboxSelected : symbols.checkboxActive;
                    const colorFn = isCursor ? colors.active : colors.secondary;
                    const label = choice.hint
                        ? `${choice.label} ${colors.muted(choice.hint)}`
                        : choice.label;
                    process.stdout.write(`${colors.secondary(symbols.bar)}  ${colorFn(symbol)}  ${label}\n`);
                });
                hasRendered = true;
            };
            // Resume stdin if it was paused
            if (process.stdin.isTTY && process.stdin.isPaused()) {
                process.stdin.resume();
            }
            render();
            readline.emitKeypressEvents(process.stdin);
            if (process.stdin.isTTY) {
                process.stdin.setRawMode(true);
            }
            const onKeypress = (_str, key) => {
                if (key.name === 'up' || key.name === 'k') {
                    cursorIndex = Math.max(0, cursorIndex - 1);
                    render();
                }
                else if (key.name === 'down' || key.name === 'j') {
                    cursorIndex = Math.min(choices.length - 1, cursorIndex + 1);
                    render();
                }
                else if (key.name === 'space') {
                    if (selected.has(cursorIndex)) {
                        selected.delete(cursorIndex);
                    }
                    else {
                        selected.add(cursorIndex);
                    }
                    render();
                }
                else if (key.name === 'return') {
                    cleanup();
                    // Sort indices to get consistent order for both values and labels
                    const sortedIndices = Array.from(selected).sort((a, b) => a - b);
                    const values = sortedIndices
                        .map((i) => choices[i]?.value)
                        .filter((v) => v !== undefined);
                    const labels = sortedIndices
                        .map((i) => choices[i]?.label)
                        .filter((l) => l !== undefined);
                    // Clear all lines (message + all choices)
                    const totalLines = choices.length + 1;
                    for (let i = 0; i < totalLines; i++) {
                        readline.moveCursor(process.stdout, 0, -1);
                        readline.clearLine(process.stdout, 0);
                    }
                    readline.cursorTo(process.stdout, 0);
                    // Show completed state
                    const displayValue = labels.length > 0 ? labels.join(', ') : 'None';
                    process.stdout.write(`${colors.completed(symbols.completed)}  ${message}\n${colors.secondary(symbols.bar)}  ${colors.muted(displayValue)}\n${colors.secondary(symbols.bar)}\n`);
                    this.states.push({
                        type: 'completed',
                        message,
                        value: values,
                    });
                    resolve(values);
                }
                else if (key.ctrl && key.name === 'c') {
                    cleanup();
                    console.log('\n');
                    this.cancel('Operation cancelled');
                    reject(new Error('User cancelled'));
                }
            };
            const cleanup = () => {
                process.stdin.removeListener('keypress', onKeypress);
                if (process.stdin.isTTY) {
                    process.stdin.setRawMode(false);
                }
            };
            process.stdin.on('keypress', onKeypress);
        });
    }
    /**
     * Cancel the flow
     */
    cancel(message) {
        console.log(`${colors.error(symbols.cancel)}  ${message}\n`);
        process.exit(0);
    }
}
/**
 * Create a new prompt flow instance
 */
export function createPrompt() {
    return new PromptFlow();
}
//# sourceMappingURL=prompt.js.map