import { jsx as _jsx } from "react/jsx-runtime";
import { KittySequenceOverflowEvent, logKittySequenceOverflow, } from '@google/gemini-cli-core';
import { useStdin } from 'ink';
import { createContext, useCallback, useContext, useEffect, useRef, } from 'react';
import readline from 'node:readline';
import { PassThrough } from 'node:stream';
import { BACKSLASH_ENTER_DETECTION_WINDOW_MS, CHAR_CODE_ESC, KITTY_CTRL_C, KITTY_KEYCODE_BACKSPACE, KITTY_KEYCODE_ENTER, KITTY_KEYCODE_NUMPAD_ENTER, KITTY_KEYCODE_TAB, MAX_KITTY_SEQUENCE_LENGTH, KITTY_MODIFIER_BASE, KITTY_MODIFIER_EVENT_TYPES_OFFSET, MODIFIER_SHIFT_BIT, MODIFIER_ALT_BIT, MODIFIER_CTRL_BIT, } from '../utils/platformConstants.js';
import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus.js';
const ESC = '\u001B';
export const PASTE_MODE_PREFIX = `${ESC}[200~`;
export const PASTE_MODE_SUFFIX = `${ESC}[201~`;
export const DRAG_COMPLETION_TIMEOUT_MS = 100; // Broadcast full path after 100ms if no more input
export const SINGLE_QUOTE = "'";
export const DOUBLE_QUOTE = '"';
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, kittyProtocolEnabled, config, debugKeystrokeLogging, }) {
    const { stdin, setRawMode } = useStdin();
    const subscribers = useRef(new Set()).current;
    const isDraggingRef = useRef(false);
    const dragBufferRef = useRef('');
    const draggingTimerRef = useRef(null);
    const subscribe = useCallback((handler) => {
        subscribers.add(handler);
    }, [subscribers]);
    const unsubscribe = useCallback((handler) => {
        subscribers.delete(handler);
    }, [subscribers]);
    useEffect(() => {
        const clearDraggingTimer = () => {
            if (draggingTimerRef.current) {
                clearTimeout(draggingTimerRef.current);
                draggingTimerRef.current = null;
            }
        };
        const wasRaw = stdin.isRaw;
        if (wasRaw === false) {
            setRawMode(true);
        }
        const keypressStream = new PassThrough();
        let usePassthrough = false;
        const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
        if (nodeMajorVersion < 20 ||
            process.env['PASTE_WORKAROUND'] === '1' ||
            process.env['PASTE_WORKAROUND'] === 'true') {
            usePassthrough = true;
        }
        let isPaste = false;
        let pasteBuffer = Buffer.alloc(0);
        let kittySequenceBuffer = '';
        let backslashTimeout = null;
        let waitingForEnterAfterBackslash = false;
        // Parse a single complete kitty sequence from the start (prefix) of the
        // buffer and return both the Key and the number of characters consumed.
        // This lets us "peel off" one complete event when multiple sequences arrive
        // in a single chunk, preventing buffer overflow and fragmentation.
        // Parse a single complete kitty/parameterized/legacy sequence from the start
        // of the buffer and return both the parsed Key and the number of characters
        // consumed. This enables peel-and-continue parsing for batched input.
        const parseKittyPrefix = (buffer) => {
            // In older terminals ESC [ Z was used as Cursor Backward Tabulation (CBT)
            // In newer terminals the same functionality of key combination for moving
            // backward through focusable elements is Shift+Tab, hence we will
            // map ESC [ Z to Shift+Tab
            // 0) Reverse Tab (legacy): ESC [ Z
            //    Treat as Shift+Tab for UI purposes.
            //    Regex parts:
            //    ^     - start of buffer
            //    ESC [ - CSI introducer
            //    Z     - legacy reverse tab
            const revTabLegacy = new RegExp(`^${ESC}\\[Z`);
            let m = buffer.match(revTabLegacy);
            if (m) {
                return {
                    key: {
                        name: 'tab',
                        ctrl: false,
                        meta: false,
                        shift: true,
                        paste: false,
                        sequence: buffer.slice(0, m[0].length),
                        kittyProtocol: true,
                    },
                    length: m[0].length,
                };
            }
            // 1) Reverse Tab (parameterized): ESC [ 1 ; <mods> Z
            //    Parameterized reverse Tab: ESC [ 1 ; <mods> Z
            const revTabParam = new RegExp(`^${ESC}\\[1;(\\d+)Z`);
            m = buffer.match(revTabParam);
            if (m) {
                let mods = parseInt(m[1], 10);
                if (mods >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
                    mods -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
                }
                const bits = mods - KITTY_MODIFIER_BASE;
                const alt = (bits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
                const ctrl = (bits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
                return {
                    key: {
                        name: 'tab',
                        ctrl,
                        meta: alt,
                        // Reverse tab implies Shift behavior; force shift regardless of mods
                        shift: true,
                        paste: false,
                        sequence: buffer.slice(0, m[0].length),
                        kittyProtocol: true,
                    },
                    length: m[0].length,
                };
            }
            // 2) Parameterized functional: ESC [ 1 ; <mods> (A|B|C|D|H|F|P|Q|R|S)
            // 2) Parameterized functional: ESC [ 1 ; <mods> (A|B|C|D|H|F|P|Q|R|S)
            //    Arrows, Home/End, F1–F4 with modifiers encoded in <mods>.
            const arrowPrefix = new RegExp(`^${ESC}\\[1;(\\d+)([ABCDHFPQSR])`);
            m = buffer.match(arrowPrefix);
            if (m) {
                let mods = parseInt(m[1], 10);
                if (mods >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
                    mods -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
                }
                const bits = mods - KITTY_MODIFIER_BASE;
                const shift = (bits & MODIFIER_SHIFT_BIT) === MODIFIER_SHIFT_BIT;
                const alt = (bits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
                const ctrl = (bits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
                const sym = m[2];
                const symbolToName = {
                    A: 'up',
                    B: 'down',
                    C: 'right',
                    D: 'left',
                    H: 'home',
                    F: 'end',
                    P: 'f1',
                    Q: 'f2',
                    R: 'f3',
                    S: 'f4',
                };
                const name = symbolToName[sym] || '';
                if (!name)
                    return null;
                return {
                    key: {
                        name,
                        ctrl,
                        meta: alt,
                        shift,
                        paste: false,
                        sequence: buffer.slice(0, m[0].length),
                        kittyProtocol: true,
                    },
                    length: m[0].length,
                };
            }
            // 3) CSI-u form: ESC [ <code> ; <mods> (u|~)
            // 3) CSI-u and tilde-coded functional keys: ESC [ <code> ; <mods> (u|~)
            //    'u' terminator: Kitty CSI-u; '~' terminator: tilde-coded function keys.
            const csiUPrefix = new RegExp(`^${ESC}\\[(\\d+)(;(\\d+))?([u~])`);
            m = buffer.match(csiUPrefix);
            if (m) {
                const keyCode = parseInt(m[1], 10);
                let modifiers = m[3] ? parseInt(m[3], 10) : KITTY_MODIFIER_BASE;
                if (modifiers >= KITTY_MODIFIER_EVENT_TYPES_OFFSET) {
                    modifiers -= KITTY_MODIFIER_EVENT_TYPES_OFFSET;
                }
                const modifierBits = modifiers - KITTY_MODIFIER_BASE;
                const shift = (modifierBits & MODIFIER_SHIFT_BIT) === MODIFIER_SHIFT_BIT;
                const alt = (modifierBits & MODIFIER_ALT_BIT) === MODIFIER_ALT_BIT;
                const ctrl = (modifierBits & MODIFIER_CTRL_BIT) === MODIFIER_CTRL_BIT;
                const terminator = m[4];
                // Tilde-coded functional keys (Delete, Insert, PageUp/Down, Home/End)
                if (terminator === '~') {
                    let name = null;
                    switch (keyCode) {
                        case 1:
                            name = 'home';
                            break;
                        case 2:
                            name = 'insert';
                            break;
                        case 3:
                            name = 'delete';
                            break;
                        case 4:
                            name = 'end';
                            break;
                        case 5:
                            name = 'pageup';
                            break;
                        case 6:
                            name = 'pagedown';
                            break;
                        default:
                            break;
                    }
                    if (name) {
                        return {
                            key: {
                                name,
                                ctrl,
                                meta: alt,
                                shift,
                                paste: false,
                                sequence: buffer.slice(0, m[0].length),
                                kittyProtocol: true,
                            },
                            length: m[0].length,
                        };
                    }
                }
                const kittyKeyCodeToName = {
                    [CHAR_CODE_ESC]: 'escape',
                    [KITTY_KEYCODE_TAB]: 'tab',
                    [KITTY_KEYCODE_BACKSPACE]: 'backspace',
                    [KITTY_KEYCODE_ENTER]: 'return',
                    [KITTY_KEYCODE_NUMPAD_ENTER]: 'return',
                };
                const name = kittyKeyCodeToName[keyCode];
                if (name) {
                    return {
                        key: {
                            name,
                            ctrl,
                            meta: alt,
                            shift,
                            paste: false,
                            sequence: buffer.slice(0, m[0].length),
                            kittyProtocol: true,
                        },
                        length: m[0].length,
                    };
                }
                // Ctrl+letters
                if (ctrl &&
                    keyCode >= 'a'.charCodeAt(0) &&
                    keyCode <= 'z'.charCodeAt(0)) {
                    const letter = String.fromCharCode(keyCode);
                    return {
                        key: {
                            name: letter,
                            ctrl: true,
                            meta: alt,
                            shift,
                            paste: false,
                            sequence: buffer.slice(0, m[0].length),
                            kittyProtocol: true,
                        },
                        length: m[0].length,
                    };
                }
            }
            // 4) Legacy function keys (no parameters): ESC [ (A|B|C|D|H|F)
            //    Arrows + Home/End without modifiers.
            const legacyFuncKey = new RegExp(`^${ESC}\\[([ABCDHF])`);
            m = buffer.match(legacyFuncKey);
            if (m) {
                const sym = m[1];
                const nameMap = {
                    A: 'up',
                    B: 'down',
                    C: 'right',
                    D: 'left',
                    H: 'home',
                    F: 'end',
                };
                const name = nameMap[sym];
                return {
                    key: {
                        name,
                        ctrl: false,
                        meta: false,
                        shift: false,
                        paste: false,
                        sequence: buffer.slice(0, m[0].length),
                        kittyProtocol: true,
                    },
                    length: m[0].length,
                };
            }
            return null;
        };
        const broadcast = (key) => {
            for (const handler of subscribers) {
                handler(key);
            }
        };
        const handleKeypress = (_, key) => {
            if (key.sequence === FOCUS_IN || key.sequence === FOCUS_OUT) {
                return;
            }
            if (key.name === 'paste-start') {
                isPaste = true;
                return;
            }
            if (key.name === 'paste-end') {
                isPaste = false;
                broadcast({
                    name: '',
                    ctrl: false,
                    meta: false,
                    shift: false,
                    paste: true,
                    sequence: pasteBuffer.toString(),
                });
                pasteBuffer = Buffer.alloc(0);
                return;
            }
            if (isPaste) {
                pasteBuffer = Buffer.concat([pasteBuffer, Buffer.from(key.sequence)]);
                return;
            }
            if (key.sequence === SINGLE_QUOTE ||
                key.sequence === DOUBLE_QUOTE ||
                isDraggingRef.current) {
                isDraggingRef.current = true;
                dragBufferRef.current += key.sequence;
                clearDraggingTimer();
                draggingTimerRef.current = setTimeout(() => {
                    isDraggingRef.current = false;
                    const seq = dragBufferRef.current;
                    dragBufferRef.current = '';
                    if (seq) {
                        broadcast({ ...key, name: '', paste: true, sequence: seq });
                    }
                }, DRAG_COMPLETION_TIMEOUT_MS);
                return;
            }
            if (key.name === 'return' && waitingForEnterAfterBackslash) {
                if (backslashTimeout) {
                    clearTimeout(backslashTimeout);
                    backslashTimeout = null;
                }
                waitingForEnterAfterBackslash = false;
                broadcast({
                    ...key,
                    shift: true,
                    sequence: '\r', // Corrected escaping for newline
                });
                return;
            }
            if (key.sequence === '\\' && !key.name) {
                // Corrected escaping for backslash
                waitingForEnterAfterBackslash = true;
                backslashTimeout = setTimeout(() => {
                    waitingForEnterAfterBackslash = false;
                    backslashTimeout = null;
                    broadcast(key);
                }, BACKSLASH_ENTER_DETECTION_WINDOW_MS);
                return;
            }
            if (waitingForEnterAfterBackslash && key.name !== 'return') {
                if (backslashTimeout) {
                    clearTimeout(backslashTimeout);
                    backslashTimeout = null;
                }
                waitingForEnterAfterBackslash = false;
                broadcast({
                    name: '',
                    sequence: '\\',
                    ctrl: false,
                    meta: false,
                    shift: false,
                    paste: false,
                });
            }
            if (['up', 'down', 'left', 'right'].includes(key.name)) {
                broadcast(key);
                return;
            }
            if ((key.ctrl && key.name === 'c') ||
                key.sequence === `${ESC}${KITTY_CTRL_C}`) {
                if (kittySequenceBuffer && debugKeystrokeLogging) {
                    console.log('[DEBUG] Kitty buffer cleared on Ctrl+C:', kittySequenceBuffer);
                }
                kittySequenceBuffer = '';
                if (key.sequence === `${ESC}${KITTY_CTRL_C}`) {
                    broadcast({
                        name: 'c',
                        ctrl: true,
                        meta: false,
                        shift: false,
                        paste: false,
                        sequence: key.sequence,
                        kittyProtocol: true,
                    });
                }
                else {
                    broadcast(key);
                }
                return;
            }
            if (kittyProtocolEnabled) {
                if (kittySequenceBuffer ||
                    (key.sequence.startsWith(`${ESC}[`) &&
                        !key.sequence.startsWith(PASTE_MODE_PREFIX) &&
                        !key.sequence.startsWith(PASTE_MODE_SUFFIX) &&
                        !key.sequence.startsWith(FOCUS_IN) &&
                        !key.sequence.startsWith(FOCUS_OUT))) {
                    kittySequenceBuffer += key.sequence;
                    if (debugKeystrokeLogging) {
                        console.log('[DEBUG] Kitty buffer accumulating:', kittySequenceBuffer);
                    }
                    // Try to peel off as many complete sequences as are available at the
                    // start of the buffer. This handles batched inputs cleanly. If the
                    // prefix is incomplete or invalid, skip to the next CSI introducer
                    // (ESC[) so that a following valid sequence can still be parsed.
                    let parsedAny = false;
                    while (kittySequenceBuffer) {
                        const parsed = parseKittyPrefix(kittySequenceBuffer);
                        if (!parsed) {
                            // Look for the next potential CSI start beyond index 0
                            const nextStart = kittySequenceBuffer.indexOf(`${ESC}[`, 1);
                            if (nextStart > 0) {
                                if (debugKeystrokeLogging) {
                                    console.log('[DEBUG] Skipping incomplete/invalid CSI prefix:', kittySequenceBuffer.slice(0, nextStart));
                                }
                                kittySequenceBuffer = kittySequenceBuffer.slice(nextStart);
                                continue;
                            }
                            break;
                        }
                        if (debugKeystrokeLogging) {
                            const parsedSequence = kittySequenceBuffer.slice(0, parsed.length);
                            if (kittySequenceBuffer.length > parsed.length) {
                                console.log('[DEBUG] Kitty sequence parsed successfully (prefix):', parsedSequence);
                            }
                            else {
                                console.log('[DEBUG] Kitty sequence parsed successfully:', parsedSequence);
                            }
                        }
                        // Consume the parsed prefix and broadcast it.
                        kittySequenceBuffer = kittySequenceBuffer.slice(parsed.length);
                        broadcast(parsed.key);
                        parsedAny = true;
                    }
                    if (parsedAny)
                        return;
                    if (config?.getDebugMode() || debugKeystrokeLogging) {
                        const codes = Array.from(kittySequenceBuffer).map((ch) => ch.charCodeAt(0));
                        console.warn('Kitty sequence buffer has char codes:', codes);
                    }
                    if (kittySequenceBuffer.length > MAX_KITTY_SEQUENCE_LENGTH) {
                        if (debugKeystrokeLogging) {
                            console.log('[DEBUG] Kitty buffer overflow, clearing:', kittySequenceBuffer);
                        }
                        if (config) {
                            const event = new KittySequenceOverflowEvent(kittySequenceBuffer.length, kittySequenceBuffer);
                            logKittySequenceOverflow(config, event);
                        }
                        kittySequenceBuffer = '';
                    }
                    else {
                        return;
                    }
                }
            }
            if (key.name === 'return' && key.sequence === `${ESC}\r`) {
                key.meta = true;
            }
            broadcast({ ...key, paste: isPaste });
        };
        const handleRawKeypress = (data) => {
            const pasteModePrefixBuffer = Buffer.from(PASTE_MODE_PREFIX);
            const pasteModeSuffixBuffer = Buffer.from(PASTE_MODE_SUFFIX);
            let pos = 0;
            while (pos < data.length) {
                const prefixPos = data.indexOf(pasteModePrefixBuffer, pos);
                const suffixPos = data.indexOf(pasteModeSuffixBuffer, pos);
                const isPrefixNext = prefixPos !== -1 && (suffixPos === -1 || prefixPos < suffixPos);
                const isSuffixNext = suffixPos !== -1 && (prefixPos === -1 || suffixPos < prefixPos);
                let nextMarkerPos = -1;
                let markerLength = 0;
                if (isPrefixNext) {
                    nextMarkerPos = prefixPos;
                }
                else if (isSuffixNext) {
                    nextMarkerPos = suffixPos;
                }
                markerLength = pasteModeSuffixBuffer.length;
                if (nextMarkerPos === -1) {
                    keypressStream.write(data.slice(pos));
                    return;
                }
                const nextData = data.slice(pos, nextMarkerPos);
                if (nextData.length > 0) {
                    keypressStream.write(nextData);
                }
                const createPasteKeyEvent = (name) => ({
                    name,
                    ctrl: false,
                    meta: false,
                    shift: false,
                    paste: false,
                    sequence: '',
                });
                if (isPrefixNext) {
                    handleKeypress(undefined, createPasteKeyEvent('paste-start'));
                }
                else if (isSuffixNext) {
                    handleKeypress(undefined, createPasteKeyEvent('paste-end'));
                }
                pos = nextMarkerPos + markerLength;
            }
        };
        let rl;
        if (usePassthrough) {
            rl = readline.createInterface({
                input: keypressStream,
                escapeCodeTimeout: 0,
            });
            readline.emitKeypressEvents(keypressStream, rl);
            keypressStream.on('keypress', handleKeypress);
            stdin.on('data', handleRawKeypress);
        }
        else {
            rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 0 });
            readline.emitKeypressEvents(stdin, rl);
            stdin.on('keypress', handleKeypress);
        }
        return () => {
            if (usePassthrough) {
                keypressStream.removeListener('keypress', handleKeypress);
                stdin.removeListener('data', handleRawKeypress);
            }
            else {
                stdin.removeListener('keypress', handleKeypress);
            }
            rl.close();
            // Restore the terminal to its original state.
            if (wasRaw === false) {
                setRawMode(false);
            }
            if (backslashTimeout) {
                clearTimeout(backslashTimeout);
                backslashTimeout = null;
            }
            // Flush any pending paste data to avoid data loss on exit.
            if (isPaste) {
                broadcast({
                    name: '',
                    ctrl: false,
                    meta: false,
                    shift: false,
                    paste: true,
                    sequence: pasteBuffer.toString(),
                });
                pasteBuffer = Buffer.alloc(0);
            }
            if (draggingTimerRef.current) {
                clearTimeout(draggingTimerRef.current);
                draggingTimerRef.current = null;
            }
            if (isDraggingRef.current && dragBufferRef.current) {
                broadcast({
                    name: '',
                    ctrl: false,
                    meta: false,
                    shift: false,
                    paste: true,
                    sequence: dragBufferRef.current,
                });
                isDraggingRef.current = false;
                dragBufferRef.current = '';
            }
        };
    }, [
        stdin,
        setRawMode,
        kittyProtocolEnabled,
        config,
        subscribers,
        debugKeystrokeLogging,
    ]);
    return (_jsx(KeypressContext.Provider, { value: { subscribe, unsubscribe }, children: children }));
}
//# sourceMappingURL=KeypressContext.js.map