import { jsx as _jsx } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { debugLogger } from '@google/gemini-cli-core';
import { useStdin } from 'ink';
import { createContext, useCallback, useContext, useEffect, useRef, } from 'react';
import { ESC } from '../utils/input.js';
import { parseMouseEvent } from '../utils/mouse.js';
import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus.js';
import { appEvents, AppEvent } from '../../utils/events.js';
import { terminalCapabilityManager } from '../utils/terminalCapabilityManager.js';
export const BACKSLASH_ENTER_TIMEOUT = 5;
export const ESC_TIMEOUT = 50;
export const PASTE_TIMEOUT = 30_000;
export const FAST_RETURN_TIMEOUT = 30;
// Parse the key itself
const KEY_INFO_MAP = {
    '[200~': { name: 'paste-start' },
    '[201~': { name: 'paste-end' },
    '[[A': { name: 'f1' },
    '[[B': { name: 'f2' },
    '[[C': { name: 'f3' },
    '[[D': { name: 'f4' },
    '[[E': { name: 'f5' },
    '[1~': { name: 'home' },
    '[2~': { name: 'insert' },
    '[3~': { name: 'delete' },
    '[4~': { name: 'end' },
    '[5~': { name: 'pageup' },
    '[6~': { name: 'pagedown' },
    '[7~': { name: 'home' },
    '[8~': { name: 'end' },
    '[11~': { name: 'f1' },
    '[12~': { name: 'f2' },
    '[13~': { name: 'f3' },
    '[14~': { name: 'f4' },
    '[15~': { name: 'f5' },
    '[17~': { name: 'f6' },
    '[18~': { name: 'f7' },
    '[19~': { name: 'f8' },
    '[20~': { name: 'f9' },
    '[21~': { name: 'f10' },
    '[23~': { name: 'f11' },
    '[24~': { name: 'f12' },
    '[A': { name: 'up' },
    '[B': { name: 'down' },
    '[C': { name: 'right' },
    '[D': { name: 'left' },
    '[E': { name: 'clear' },
    '[F': { name: 'end' },
    '[H': { name: 'home' },
    '[P': { name: 'f1' },
    '[Q': { name: 'f2' },
    '[R': { name: 'f3' },
    '[S': { name: 'f4' },
    OA: { name: 'up' },
    OB: { name: 'down' },
    OC: { name: 'right' },
    OD: { name: 'left' },
    OE: { name: 'clear' },
    OF: { name: 'end' },
    OH: { name: 'home' },
    OP: { name: 'f1' },
    OQ: { name: 'f2' },
    OR: { name: 'f3' },
    OS: { name: 'f4' },
    '[[5~': { name: 'pageup' },
    '[[6~': { name: 'pagedown' },
    '[9u': { name: 'tab' },
    '[13u': { name: 'return' },
    '[27u': { name: 'escape' },
    '[32u': { name: 'space' },
    '[127u': { name: 'backspace' },
    '[57414u': { name: 'return' }, // Numpad Enter
    '[a': { name: 'up', shift: true },
    '[b': { name: 'down', shift: true },
    '[c': { name: 'right', shift: true },
    '[d': { name: 'left', shift: true },
    '[e': { name: 'clear', shift: true },
    '[2$': { name: 'insert', shift: true },
    '[3$': { name: 'delete', shift: true },
    '[5$': { name: 'pageup', shift: true },
    '[6$': { name: 'pagedown', shift: true },
    '[7$': { name: 'home', shift: true },
    '[8$': { name: 'end', shift: true },
    '[Z': { name: 'tab', shift: true },
    Oa: { name: 'up', ctrl: true },
    Ob: { name: 'down', ctrl: true },
    Oc: { name: 'right', ctrl: true },
    Od: { name: 'left', ctrl: true },
    Oe: { name: 'clear', ctrl: true },
    '[2^': { name: 'insert', ctrl: true },
    '[3^': { name: 'delete', ctrl: true },
    '[5^': { name: 'pageup', ctrl: true },
    '[6^': { name: 'pagedown', ctrl: true },
    '[7^': { name: 'home', ctrl: true },
    '[8^': { name: 'end', ctrl: true },
};
const kUTF16SurrogateThreshold = 0x10000; // 2 ** 16
function charLengthAt(str, i) {
    if (str.length <= i) {
        // Pretend to move to the right. This is necessary to autocomplete while
        // moving to the right.
        return 1;
    }
    const code = str.codePointAt(i);
    return code !== undefined && code >= kUTF16SurrogateThreshold ? 2 : 1;
}
// Note: we do not convert alt+z, alt+shift+z, or alt+v here
// because mac users have alternative hotkeys.
const MAC_ALT_KEY_CHARACTER_MAP = {
    '\u222B': 'b', // "∫" back one word
    '\u0192': 'f', // "ƒ" forward one word
    '\u00B5': 'm', // "µ" toggle markup view
};
function nonKeyboardEventFilter(keypressHandler) {
    return (key) => {
        if (!parseMouseEvent(key.sequence) &&
            key.sequence !== FOCUS_IN &&
            key.sequence !== FOCUS_OUT) {
            keypressHandler(key);
        }
    };
}
/**
 * Converts return keys pressed quickly after other keys into plain
 * insertable return characters.
 *
 * This is to accommodate older terminals that paste text without bracketing.
 */
function bufferFastReturn(keypressHandler) {
    let lastKeyTime = 0;
    return (key) => {
        const now = Date.now();
        if (key.name === 'return' && now - lastKeyTime <= FAST_RETURN_TIMEOUT) {
            keypressHandler({
                ...key,
                name: 'return',
                shift: true, // to make it a newline, not a submission
                alt: false,
                ctrl: false,
                cmd: false,
                sequence: '\r',
                insertable: true,
            });
        }
        else {
            keypressHandler(key);
        }
        lastKeyTime = now;
    };
}
/**
 * Buffers "/" keys to see if they are followed return.
 * Will flush the buffer if no data is received for DRAG_COMPLETION_TIMEOUT_MS
 * or when a null key is received.
 */
function bufferBackslashEnter(keypressHandler) {
    const bufferer = (function* () {
        while (true) {
            const key = yield;
            if (key == null) {
                continue;
            }
            else if (key.sequence !== '\\') {
                keypressHandler(key);
                continue;
            }
            const timeoutId = setTimeout(() => bufferer.next(null), BACKSLASH_ENTER_TIMEOUT);
            const nextKey = yield;
            clearTimeout(timeoutId);
            if (nextKey === null) {
                keypressHandler(key);
            }
            else if (nextKey.name === 'return') {
                keypressHandler({
                    ...nextKey,
                    shift: true,
                    sequence: '\r', // Corrected escaping for newline
                });
            }
            else {
                keypressHandler(key);
                keypressHandler(nextKey);
            }
        }
    })();
    bufferer.next(); // prime the generator so it starts listening.
    return (key) => {
        bufferer.next(key);
    };
}
/**
 * Buffers paste events between paste-start and paste-end sequences.
 * Will flush the buffer if no data is received for PASTE_TIMEOUT ms or
 * when a null key is received.
 */
function bufferPaste(keypressHandler) {
    const bufferer = (function* () {
        while (true) {
            let key = yield;
            if (key === null) {
                continue;
            }
            else if (key.name !== 'paste-start') {
                keypressHandler(key);
                continue;
            }
            let buffer = '';
            while (true) {
                const timeoutId = setTimeout(() => bufferer.next(null), PASTE_TIMEOUT);
                key = yield;
                clearTimeout(timeoutId);
                if (key === null) {
                    appEvents.emit(AppEvent.PasteTimeout);
                    break;
                }
                if (key.name === 'paste-end') {
                    break;
                }
                buffer += key.sequence;
            }
            if (buffer.length > 0) {
                keypressHandler({
                    name: 'paste',
                    shift: false,
                    alt: false,
                    ctrl: false,
                    cmd: false,
                    insertable: true,
                    sequence: buffer,
                });
            }
        }
    })();
    bufferer.next(); // prime the generator so it starts listening.
    return (key) => {
        bufferer.next(key);
    };
}
/**
 * Turns raw data strings into keypress events sent to the provided handler.
 * Buffers escape sequences until a full sequence is received or
 * until a timeout occurs.
 */
function createDataListener(keypressHandler) {
    const parser = emitKeys(keypressHandler);
    parser.next(); // prime the generator so it starts listening.
    let timeoutId;
    return (data) => {
        clearTimeout(timeoutId);
        for (const char of data) {
            parser.next(char);
        }
        if (data.length !== 0) {
            timeoutId = setTimeout(() => parser.next(''), ESC_TIMEOUT);
        }
    };
}
/**
 * Translates raw keypress characters into key events.
 * Buffers escape sequences until a full sequence is received or
 * until an empty string is sent to indicate a timeout.
 */
function* emitKeys(keypressHandler) {
    while (true) {
        let ch = yield;
        let sequence = ch;
        let escaped = false;
        let name = undefined;
        let shift = false;
        let alt = false;
        let ctrl = false;
        let cmd = false;
        let code = undefined;
        let insertable = false;
        if (ch === ESC) {
            escaped = true;
            ch = yield;
            sequence += ch;
            if (ch === ESC) {
                ch = yield;
                sequence += ch;
            }
        }
        if (escaped && (ch === 'O' || ch === '[' || ch === ']')) {
            // ANSI escape sequence
            code = ch;
            let modifier = 0;
            if (ch === ']') {
                // OSC sequence
                // ESC ] <params> ; <data> BEL
                // ESC ] <params> ; <data> ESC \
                let buffer = '';
                // Read until BEL, `ESC \`, or timeout (empty string)
                while (true) {
                    const next = yield;
                    if (next === '' || next === '\u0007') {
                        break;
                    }
                    else if (next === ESC) {
                        const afterEsc = yield;
                        if (afterEsc === '' || afterEsc === '\\') {
                            break;
                        }
                        buffer += next + afterEsc;
                        continue;
                    }
                    buffer += next;
                }
                // Check for OSC 52 (Clipboard) response
                // Format: 52;c;<base64> or 52;p;<base64>
                const match = /^52;[cp];(.*)$/.exec(buffer);
                if (match) {
                    try {
                        const base64Data = match[1];
                        const decoded = Buffer.from(base64Data, 'base64').toString('utf-8');
                        keypressHandler({
                            name: 'paste',
                            shift: false,
                            alt: false,
                            ctrl: false,
                            cmd: false,
                            insertable: true,
                            sequence: decoded,
                        });
                    }
                    catch (_e) {
                        debugLogger.log('Failed to decode OSC 52 clipboard data');
                    }
                }
                continue; // resume main loop
            }
            else if (ch === 'O') {
                // ESC O letter
                // ESC O modifier letter
                ch = yield;
                sequence += ch;
                if (ch >= '0' && ch <= '9') {
                    modifier = parseInt(ch, 10) - 1;
                    ch = yield;
                    sequence += ch;
                }
                code += ch;
            }
            else if (ch === '[') {
                // ESC [ letter
                // ESC [ modifier letter
                // ESC [ [ modifier letter
                // ESC [ [ num char
                ch = yield;
                sequence += ch;
                if (ch === '[') {
                    // \x1b[[A
                    //      ^--- escape codes might have a second bracket
                    code += ch;
                    ch = yield;
                    sequence += ch;
                }
                /*
                 * Here and later we try to buffer just enough data to get
                 * a complete ascii sequence.
                 *
                 * We have basically two classes of ascii characters to process:
                 *
                 *
                 * 1. `\x1b[24;5~` should be parsed as { code: '[24~', modifier: 5 }
                 *
                 * This particular example is featuring Ctrl+F12 in xterm.
                 *
                 *  - `;5` part is optional, e.g. it could be `\x1b[24~`
                 *  - first part can contain one or two digits
                 *  - there is also special case when there can be 3 digits
                 *    but without modifier. They are the case of paste bracket mode
                 *
                 * So the generic regexp is like /^(?:\d\d?(;\d)?[~^$]|\d{3}~)$/
                 *
                 *
                 * 2. `\x1b[1;5H` should be parsed as { code: '[H', modifier: 5 }
                 *
                 * This particular example is featuring Ctrl+Home in xterm.
                 *
                 *  - `1;5` part is optional, e.g. it could be `\x1b[H`
                 *  - `1;` part is optional, e.g. it could be `\x1b[5H`
                 *
                 * So the generic regexp is like /^((\d;)?\d)?[A-Za-z]$/
                 *
                 */
                const cmdStart = sequence.length - 1;
                // collect as many digits as possible
                while (ch >= '0' && ch <= '9') {
                    ch = yield;
                    sequence += ch;
                }
                // skip modifier
                if (ch === ';') {
                    while (ch === ';') {
                        ch = yield;
                        sequence += ch;
                        // collect as many digits as possible
                        while (ch >= '0' && ch <= '9') {
                            ch = yield;
                            sequence += ch;
                        }
                    }
                }
                else if (ch === '<') {
                    // SGR mouse mode
                    ch = yield;
                    sequence += ch;
                    // Don't skip on empty string here to avoid timeouts on slow events.
                    while (ch === '' || ch === ';' || (ch >= '0' && ch <= '9')) {
                        ch = yield;
                        sequence += ch;
                    }
                }
                else if (ch === 'M') {
                    // X11 mouse mode
                    // three characters after 'M'
                    ch = yield;
                    sequence += ch;
                    ch = yield;
                    sequence += ch;
                    ch = yield;
                    sequence += ch;
                }
                /*
                 * We buffered enough data, now trying to extract code
                 * and modifier from it
                 */
                const cmd = sequence.slice(cmdStart);
                let match;
                if ((match = /^(\d+)(?:;(\d+))?(?:;(\d+))?([~^$u])$/.exec(cmd))) {
                    if (match[1] === '27' && match[3] && match[4] === '~') {
                        // modifyOtherKeys format: CSI 27 ; modifier ; key ~
                        // Treat as CSI u: key + 'u'
                        code += match[3] + 'u';
                        modifier = parseInt(match[2] ?? '1', 10) - 1;
                    }
                    else {
                        code += match[1] + match[4];
                        // Defaults to '1' if no modifier exists, resulting in a 0 modifier value
                        modifier = parseInt(match[2] ?? '1', 10) - 1;
                    }
                }
                else if ((match = /^(\d+)?(?:;(\d+))?([A-Za-z])$/.exec(cmd))) {
                    code += match[3];
                    modifier = parseInt(match[2] ?? match[1] ?? '1', 10) - 1;
                }
                else {
                    code += cmd;
                }
            }
            // Parse the key modifier
            shift = !!(modifier & 1);
            alt = !!(modifier & 2);
            ctrl = !!(modifier & 4);
            cmd = !!(modifier & 8);
            const keyInfo = KEY_INFO_MAP[code];
            if (keyInfo) {
                name = keyInfo.name;
                if (keyInfo.shift) {
                    shift = true;
                }
                if (keyInfo.ctrl) {
                    ctrl = true;
                }
                if (name === 'space' && !ctrl && !cmd && !alt) {
                    sequence = ' ';
                    insertable = true;
                }
            }
            else {
                name = 'undefined';
                if ((ctrl || cmd || alt) &&
                    (code.endsWith('u') || code.endsWith('~'))) {
                    // CSI-u or tilde-coded functional keys: ESC [ <code> ; <mods> (u|~)
                    const codeNumber = parseInt(code.slice(1, -1), 10);
                    if (codeNumber >= 'a'.charCodeAt(0) &&
                        codeNumber <= 'z'.charCodeAt(0)) {
                        name = String.fromCharCode(codeNumber);
                    }
                }
            }
        }
        else if (ch === '\r') {
            // carriage return
            name = 'return';
            alt = escaped;
        }
        else if (escaped && ch === '\n') {
            // Alt+Enter (linefeed), should be consistent with carriage return
            name = 'return';
            alt = escaped;
        }
        else if (ch === '\t') {
            // tab
            name = 'tab';
            alt = escaped;
        }
        else if (ch === '\b' || ch === '\x7f') {
            // backspace or ctrl+h
            name = 'backspace';
            alt = escaped;
        }
        else if (ch === ESC) {
            // escape key
            name = 'escape';
            alt = escaped;
        }
        else if (ch === ' ') {
            name = 'space';
            alt = escaped;
            insertable = true;
        }
        else if (!escaped && ch <= '\x1a') {
            // ctrl+letter
            name = String.fromCharCode(ch.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
            ctrl = true;
        }
        else if (/^[0-9A-Za-z]$/.exec(ch) !== null) {
            // Letter, number, shift+letter
            name = ch.toLowerCase();
            shift = /^[A-Z]$/.exec(ch) !== null;
            alt = escaped;
            insertable = true;
        }
        else if (MAC_ALT_KEY_CHARACTER_MAP[ch]) {
            // Note: we do this even if we are not on Mac, because mac users may
            // remotely connect to non-Mac systems.
            name = MAC_ALT_KEY_CHARACTER_MAP[ch];
            alt = true;
        }
        else if (sequence === `${ESC}${ESC}`) {
            // Double escape
            name = 'escape';
            alt = true;
            // Emit first escape key here, then continue processing
            keypressHandler({
                name: 'escape',
                shift,
                alt,
                ctrl,
                cmd,
                insertable: false,
                sequence: ESC,
            });
        }
        else if (escaped) {
            // Escape sequence timeout
            name = ch.length ? undefined : 'escape';
            alt = true;
        }
        else {
            // Any other character is considered printable.
            insertable = true;
        }
        if ((sequence.length !== 0 && (name !== undefined || escaped)) ||
            charLengthAt(sequence, 0) === sequence.length) {
            keypressHandler({
                name: name || '',
                shift,
                alt,
                ctrl,
                cmd,
                insertable,
                sequence,
            });
        }
        // Unrecognized or broken escape sequence, don't emit anything
    }
}
const KeypressContext = createContext(undefined);
export function useKeypressContext() {
    const context = useContext(KeypressContext);
    if (!context) {
        throw new Error('useKeypressContext must be used within a KeypressProvider');
    }
    return context;
}
export function KeypressProvider({ children, config, debugKeystrokeLogging, }) {
    const { stdin, setRawMode } = useStdin();
    const prioritySubscribers = useRef(new Set()).current;
    const normalSubscribers = useRef(new Set()).current;
    const subscribe = useCallback((handler, priority = false) => {
        const set = priority ? prioritySubscribers : normalSubscribers;
        set.add(handler);
    }, [prioritySubscribers, normalSubscribers]);
    const unsubscribe = useCallback((handler) => {
        prioritySubscribers.delete(handler);
        normalSubscribers.delete(handler);
    }, [prioritySubscribers, normalSubscribers]);
    const broadcast = useCallback((key) => {
        // Process priority subscribers first, in reverse order (stack behavior: last subscribed is first to handle)
        const priorityHandlers = Array.from(prioritySubscribers).reverse();
        for (const handler of priorityHandlers) {
            if (handler(key) === true) {
                return;
            }
        }
        // Then process normal subscribers, also in reverse order
        const normalHandlers = Array.from(normalSubscribers).reverse();
        for (const handler of normalHandlers) {
            if (handler(key) === true) {
                return;
            }
        }
    }, [prioritySubscribers, normalSubscribers]);
    useEffect(() => {
        const wasRaw = stdin.isRaw;
        if (wasRaw === false) {
            setRawMode(true);
        }
        process.stdin.setEncoding('utf8'); // Make data events emit strings
        let processor = nonKeyboardEventFilter(broadcast);
        if (!terminalCapabilityManager.isKittyProtocolEnabled()) {
            processor = bufferFastReturn(processor);
        }
        processor = bufferBackslashEnter(processor);
        processor = bufferPaste(processor);
        let dataListener = createDataListener(processor);
        if (debugKeystrokeLogging) {
            const old = dataListener;
            dataListener = (data) => {
                if (data.length > 0) {
                    debugLogger.log(`[DEBUG] Raw StdIn: ${JSON.stringify(data)}`);
                }
                old(data);
            };
        }
        stdin.on('data', dataListener);
        return () => {
            stdin.removeListener('data', dataListener);
            if (wasRaw === false) {
                setRawMode(false);
            }
        };
    }, [stdin, setRawMode, config, debugKeystrokeLogging, broadcast]);
    return (_jsx(KeypressContext.Provider, { value: { subscribe, unsubscribe }, children: children }));
}
//# sourceMappingURL=KeypressContext.js.map