"use strict";
// src/cli/ProviderSelector.ts
// Reusable arrow-key radio menu — used by ConfigTui and any TUI that
// needs a "pick one of N options" interaction.
//
// Matches the Hermes Agent provider-selector UX:
//   - ↑↓ / j/k navigate
//   - ENTER / SPACE select
//   - Mouse click: move cursor; click same item again = select
//   - ESC cancel
//   - currently-active option marked with `(•)` and `← currently active`
//   - other options marked with `(o)`
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.selectOne = selectOne;
exports.renderMenu = renderMenu;
exports.numberedChoice = numberedChoice;
const readline = __importStar(require("readline"));
const MatrixTheme_1 = require("./MatrixTheme");
/** Drain any buffered bytes from stdin to prevent ghost-input leaking into
 *  the next readline.createInterface after raw mode is restored. */
function drainStdin() {
    try {
        let chunk;
        while ((chunk = process.stdin.read()) !== null) {
            void chunk;
        }
    }
    catch { /* stream may be unreadable */ }
}
/** Query the terminal for the current cursor row (1-indexed).
 *  Sends ANSI CPR request and parses the ESC[row;colR response.
 *  Falls back to 0 if the terminal doesn't respond within 80ms. */
function queryCursorRow() {
    return new Promise((resolve) => {
        let settled = false;
        const timeout = setTimeout(() => {
            if (!settled) {
                settled = true;
                process.stdin.removeListener('data', onData);
                resolve(0);
            }
        }, 80);
        const onData = (data) => {
            const m = data.toString().match(/\x1b\[(\d+);\d+R/);
            if (m && !settled) {
                settled = true;
                clearTimeout(timeout);
                process.stdin.removeListener('data', onData);
                resolve(parseInt(m[1], 10));
            }
        };
        process.stdin.on('data', onData);
        // eslint-disable-next-line no-control-regex
        process.stdout.write('\x1b[6n');
    });
}
/**
 * Show the menu, await user input, return the selected value (or null on ESC).
 *
 * In non-TTY contexts (CI, piped), prints the menu once and returns
 * `options[initialIndex].value` without blocking — keeps scripts working.
 */
async function selectOne(opts) {
    const activeIdx = opts.options.findIndex(o => o.value === opts.active);
    let cursor = opts.initialIndex ?? (activeIdx >= 0 ? activeIdx : 0);
    if (!process.stdin.isTTY || !process.stdout.isTTY) {
        process.stdout.write(renderMenu(opts.title, opts.options, cursor, opts.active));
        return opts.options[cursor]?.value ?? null;
    }
    return new Promise((resolve) => {
        const previousRaw = process.stdin.isRaw === true;
        try {
            readline.emitKeypressEvents(process.stdin);
            process.stdin.setRawMode(true);
        }
        catch {
            resolve(opts.options[cursor]?.value ?? null);
            return;
        }
        process.stdout.write(MatrixTheme_1.ANSI.hideCursor);
        // Enable X10 mouse click reporting (button press/release, no motion)
        process.stdout.write('\x1b[?1000h');
        let firstDraw = true;
        // menuStartRow is captured before the first draw so mouse clicks can be
        // mapped to item indices. Set to 0 until the CPR response arrives.
        let menuStartRow = 0;
        // MENU_HEADER_LINES = title + hint + blank = 3 lines before the first item
        const MENU_HEADER_LINES = 3;
        const draw = () => {
            const block = renderMenu(opts.title, opts.options, cursor, opts.active);
            if (!firstDraw) {
                const lineCount = block.split('\n').length;
                process.stdout.write(`\x1b[${lineCount}A`);
            }
            firstDraw = false;
            process.stdout.write(block);
        };
        const cleanup = () => {
            process.stdin.removeListener('keypress', onKey);
            // Disable mouse reporting before restoring raw mode
            process.stdout.write('\x1b[?1000l');
            try {
                process.stdin.setRawMode(previousRaw);
            }
            catch { /* ignore */ }
            process.stdout.write(MatrixTheme_1.ANSI.showCursor);
            process.stdout.write(MatrixTheme_1.ANSI.reset);
            // Drain buffered bytes (e.g. the \r from Enter in raw mode) to prevent
            // ghost input leaking into the next readline.createInterface call.
            drainStdin();
        };
        const onKey = (_str, key) => {
            if (!key)
                return;
            // ── Mouse click (X10 legacy: \x1b[M + 3 bytes) ─────────────────
            // eslint-disable-next-line no-control-regex
            if (key.sequence && /^\x1b\[M/.test(key.sequence) && key.sequence.length >= 6) {
                const btn = key.sequence.charCodeAt(3) - 32;
                // col = key.sequence.charCodeAt(4) - 32;  (unused)
                const row = key.sequence.charCodeAt(5) - 32; // 1-indexed terminal row
                const isPress = btn !== 3; // button 3 = release event
                if (isPress && menuStartRow > 0) {
                    const itemIndex = row - menuStartRow - MENU_HEADER_LINES;
                    if (itemIndex >= 0 && itemIndex < opts.options.length) {
                        if (cursor === itemIndex) {
                            // Second click on the already-highlighted item → select
                            cleanup();
                            process.stdout.write('\n');
                            resolve(opts.options[cursor]?.value ?? null);
                        }
                        else {
                            cursor = itemIndex;
                            draw();
                        }
                    }
                }
                return;
            }
            // ── Keyboard ────────────────────────────────────────────────────
            if (key.ctrl && key.name === 'c') {
                cleanup();
                process.stdout.write('\n');
                resolve(null);
                return;
            }
            if (key.name === 'escape') {
                cleanup();
                process.stdout.write('\n');
                resolve(null);
                return;
            }
            if (key.name === 'up' || key.name === 'k') {
                cursor = (cursor - 1 + opts.options.length) % opts.options.length;
                draw();
                return;
            }
            if (key.name === 'down' || key.name === 'j') {
                cursor = (cursor + 1) % opts.options.length;
                draw();
                return;
            }
            if (key.name === 'return' || key.name === 'space') {
                cleanup();
                process.stdout.write('\n');
                resolve(opts.options[cursor]?.value ?? null);
                return;
            }
        };
        process.stdin.on('keypress', onKey);
        // Capture the cursor row, then draw. The CPR query is async; if the
        // terminal responds quickly the row is set before the first click.
        queryCursorRow().then(row => { menuStartRow = row; });
        draw();
    });
}
/** Visible column count, excluding zero-width ANSI escape sequences. */
// eslint-disable-next-line no-control-regex
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
function visibleWidth(s) {
    return s.replace(ANSI_RE, '').length;
}
/** Truncate a string to `max` VISIBLE columns, preserving ANSI codes and
 *  appending a reset. Strings already within width are returned untouched.
 *  This is the keystone of the redraw fix: clamping every line so it never
 *  wraps guarantees logical line count == visual rows, so the cursor-up
 *  redraw (`\x1b[{n}A`) lands exactly and never drifts. */
function clampToWidth(s, max) {
    if (max <= 0 || visibleWidth(s) <= max)
        return s;
    let out = '';
    let visible = 0;
    let i = 0;
    while (i < s.length && visible < max - 1) {
        if (s[i] === '\x1b') {
            const m = s.slice(i).match(/^\x1b\[[0-9;]*[A-Za-z]/);
            if (m) {
                out += m[0];
                i += m[0].length;
                continue;
            }
        }
        out += s[i];
        visible++;
        i++;
    }
    return out + '…' + MatrixTheme_1.ANSI.reset;
}
function renderMenu(title, options, cursor, active, width = process.stdout.columns ?? 80) {
    const useColor = (0, MatrixTheme_1.supportsColor)();
    const head = useColor ? MatrixTheme_1.MATRIX.head : '';
    const body = useColor ? MatrixTheme_1.MATRIX.body : '';
    const tail = useColor ? MatrixTheme_1.MATRIX.tail : '';
    const reset = useColor ? MatrixTheme_1.ANSI.reset : '';
    // Clamp to width-1 (leaves a column of slack) and append clear-to-EOL so a
    // shorter redraw wipes any stale characters left by a longer previous frame.
    const max = Math.max(8, width - 1);
    const clearEol = useColor ? '\x1b[K' : '';
    const emit = (line) => clampToWidth(line, max) + clearEol;
    const lines = [];
    lines.push(emit(`${head}${title}${reset}`));
    lines.push(emit(`${tail}↑↓ navigate · ENTER/click select · ESC cancel${reset}`));
    lines.push(clearEol);
    for (let i = 0; i < options.length; i++) {
        const opt = options[i];
        const isCursor = i === cursor;
        const isActive = opt.value === active;
        const marker = isActive ? '(•)' : '(o)';
        const arrow = isCursor ? '▶' : ' ';
        const hint = opt.hint ? `${tail} — ${opt.hint}${reset}` : '';
        const activeTag = isActive ? `  ${tail}← currently active${reset}` : '';
        const color = isCursor ? head : body;
        lines.push(emit(`  ${color}${arrow} ${marker} ${opt.label}${reset}${hint}${activeTag}`));
    }
    lines.push(clearEol);
    return lines.join('\n');
}
/**
 * Single-keypress numbered choice prompt (no Enter needed).
 * Uses raw mode like selectOne, so no readline.createInterface interference.
 */
async function numberedChoice(question, choices) {
    const useColor = (0, MatrixTheme_1.supportsColor)();
    const head = useColor ? MatrixTheme_1.MATRIX.head : '';
    const body = useColor ? MatrixTheme_1.MATRIX.body : '';
    const tail = useColor ? MatrixTheme_1.MATRIX.tail : '';
    const reset = useColor ? MatrixTheme_1.ANSI.reset : '';
    process.stdout.write(`\n${head}${question}${reset}\n`);
    for (const c of choices) {
        process.stdout.write(`  ${body}${c.key}) ${c.label}${reset}\n`);
    }
    const keys = choices.map(c => c.key).join('/');
    process.stdout.write(`\n  ${tail}press [${keys}] · ESC cancel${reset} `);
    if (!process.stdin.isTTY) {
        process.stdout.write('\n');
        return null;
    }
    return new Promise((resolve) => {
        const prevRaw = process.stdin.isRaw === true;
        try {
            readline.emitKeypressEvents(process.stdin);
            process.stdin.setRawMode(true);
        }
        catch {
            resolve(null);
            return;
        }
        const done = (val) => {
            process.stdin.removeListener('keypress', handler);
            try {
                process.stdin.setRawMode(prevRaw);
            }
            catch { /* ignore */ }
            drainStdin();
            process.stdout.write('\n');
            resolve(val);
        };
        const handler = (_str, key) => {
            if (!key)
                return;
            if ((key.ctrl && key.name === 'c') || key.name === 'escape') {
                done(null);
                return;
            }
            // Match by key.name (e.g. '1' → name='1') or raw string
            const match = choices.find(c => c.key === key.name || c.key === _str);
            if (match) {
                done(match.key);
                return;
            }
        };
        process.stdin.on('keypress', handler);
    });
}
