import { useState, useMemo, useEffect, useRef } from "react";
import { useInput } from "ink";
import { promises as fsPromises } from "fs";
import path from "path";
import * as yaml from 'js-yaml';
import { ConfirmationService } from "../utils/confirmation-service.js";
import { useEnhancedInput } from "./use-enhanced-input.js";
import { getErrorMessage } from "../types/index.js";
import { filterCommandSuggestions } from "../ui/components/CommandSuggestions.js";
import { loadModelConfig } from "../utils/model-config.js";
// Import enhanced features
import { getSlashCommandManager } from "../commands/slash-commands.js";
import { getTTSManager } from "../input/text-to-speech.js";
import { ClientCommandDispatcher } from "../commands/client-dispatcher.js";
// Import file autocomplete
import { extractFileReference, getFileSuggestions } from "../ui/components/FileAutocomplete.js";
// Import interaction logger for session tracking
import { getInteractionLogger } from "../logging/interaction-logger.js";
// Import history manager for persistent command history
import { getHistoryManager } from "../utils/history-manager.js";
export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProcessing, setIsStreaming, setTokenCount, setProcessingTime, processingStartTime, isProcessing, isStreaming, isConfirmationActive = false, }) {
    const [showCommandSuggestions, setShowCommandSuggestions] = useState(false);
    const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
    const [showModelSelection, setShowModelSelection] = useState(false);
    const [selectedModelIndex, setSelectedModelIndex] = useState(0);
    const [showFileAutocomplete, setShowFileAutocomplete] = useState(false);
    const [selectedFileIndex, setSelectedFileIndex] = useState(0);
    const [fileSuggestions, setFileSuggestions] = useState([]);
    const [autoEditEnabled, setAutoEditEnabled] = useState(() => {
        const confirmationService = ConfirmationService.getInstance();
        const sessionFlags = confirmationService.getSessionFlags();
        return sessionFlags.allOperations;
    });
    // Track last escape time for double-escape detection
    const lastEscapeTimeRef = useRef(0);
    const DOUBLE_ESCAPE_THRESHOLD = 500; // ms
    /**
     * Save instruction to .codebuddyrules file (Claude Code-style # capture)
     */
    const saveInstructionToCodeBuddyRules = async (instruction) => {
        const codebuddyrulesPath = path.join(process.cwd(), '.codebuddyrules');
        try {
            let rules = {};
            // Load existing rules if file exists
            const exists = await fsPromises.access(codebuddyrulesPath).then(() => true).catch(() => false);
            if (exists) {
                const content = await fsPromises.readFile(codebuddyrulesPath, 'utf-8');
                try {
                    rules = yaml.load(content) || {};
                }
                catch {
                    // If parsing fails, treat existing content as raw
                    rules = { instructions: [] };
                }
            }
            // Ensure instructions array exists
            if (!rules.instructions) {
                rules.instructions = [];
            }
            // Add the new instruction if not already present
            if (!rules.instructions.includes(instruction)) {
                rules.instructions.push(instruction);
            }
            // Write back to file
            await fsPromises.writeFile(codebuddyrulesPath, yaml.dump(rules, { lineWidth: -1 }));
            return `Instruction saved to .codebuddyrules:\n  "${instruction}"`;
        }
        catch (error) {
            return `Failed to save instruction: ${getErrorMessage(error)}`;
        }
    };
    /**
     * Get the last user message from chat history for editing
     */
    const getLastUserMessage = () => {
        for (let i = chatHistory.length - 1; i >= 0; i--) {
            if (chatHistory[i].type === 'user') {
                return chatHistory[i].content;
            }
        }
        return null;
    };
    // Helper functions for handleSpecialKey
    const handleEscapeKey = (_key) => {
        if (showCommandSuggestions) {
            setShowCommandSuggestions(false);
            setSelectedCommandIndex(0);
            return true;
        }
        if (showModelSelection) {
            setShowModelSelection(false);
            setSelectedModelIndex(0);
            return true;
        }
        if (showFileAutocomplete) {
            setShowFileAutocomplete(false);
            setSelectedFileIndex(0);
            return true;
        }
        if (isProcessing || isStreaming) {
            agent.abortCurrentOperation();
            setIsProcessing(false);
            setIsStreaming(false);
            setTokenCount(0);
            setProcessingTime(0);
            processingStartTime.current = 0;
            return true;
        }
        // Double-escape detection for editing previous prompt
        const now = Date.now();
        const timeSinceLastEscape = now - lastEscapeTimeRef.current;
        lastEscapeTimeRef.current = now;
        if (timeSinceLastEscape < DOUBLE_ESCAPE_THRESHOLD && input.trim() === '') {
            // Double escape with empty input - load last user message for editing
            const lastMessage = getLastUserMessage();
            if (lastMessage) {
                setInput(lastMessage);
                setCursorPosition(lastMessage.length);
                return true;
            }
        }
        return false; // Let default escape handling work
    };
    const handleCommandSuggestionsNav = (key) => {
        const filteredSuggestions = filterCommandSuggestions(commandSuggestions, input);
        if (filteredSuggestions.length === 0) {
            setShowCommandSuggestions(false);
            setSelectedCommandIndex(0);
            return false; // Continue processing
        }
        else {
            if (key.upArrow) {
                setSelectedCommandIndex((prev) => prev === 0 ? filteredSuggestions.length - 1 : prev - 1);
                return true;
            }
            if (key.downArrow) {
                setSelectedCommandIndex((prev) => (prev + 1) % filteredSuggestions.length);
                return true;
            }
            if (key.tab || key.return) {
                const safeIndex = Math.min(selectedCommandIndex, filteredSuggestions.length - 1);
                const selectedSuggestion = filteredSuggestions[safeIndex];
                let newInput;
                if (selectedSuggestion.isArgument) {
                    // For arguments, keep the command and add the argument
                    const parts = input.trim().split(/\s+/);
                    const baseCommand = parts[0]; // e.g., "/ai-test"
                    newInput = `${baseCommand} ${selectedSuggestion.command}`;
                    // If Enter was pressed, execute the full command directly
                    if (key.return) {
                        setShowCommandSuggestions(false);
                        setSelectedCommandIndex(0);
                        clearInput();
                        // Execute the full command directly
                        handleDirectCommand(newInput);
                        return true;
                    }
                }
                else {
                    // For commands, just use the command
                    newInput = selectedSuggestion.command + " ";
                }
                setInput(newInput);
                setCursorPosition(newInput.length);
                setShowCommandSuggestions(false);
                setSelectedCommandIndex(0);
                return true;
            }
        }
        return false;
    };
    const handleModelSelectionNav = (key) => {
        if (key.upArrow) {
            setSelectedModelIndex((prev) => prev === 0 ? availableModels.length - 1 : prev - 1);
            return true;
        }
        if (key.downArrow) {
            setSelectedModelIndex((prev) => (prev + 1) % availableModels.length);
            return true;
        }
        if (key.tab || key.return) {
            const selectedModel = availableModels[selectedModelIndex];
            // Delegate to Dispatcher implicitly via handleDirectCommand? 
            // No, UI navigation logic remains here, but the action can be manual.
            // Or we can construct a command string and let dispatcher handle it.
            // But we have state setters here.
            // Let's keep UI state manipulation here for selection, but action execution via command if possible.
            // Actually, standard behavior:
            // agent.setModel(selectedModel.model);
            // updateCurrentModel(selectedModel.model);
            // ...
            // We can use a helper, but for now let's leave this UI logic as is, or use handleDirectCommand("/models " + model)
            handleDirectCommand(`/models ${selectedModel.model}`);
            setShowModelSelection(false);
            setSelectedModelIndex(0);
            return true;
        }
        return false;
    };
    const handleFileAutocompleteNav = (key) => {
        if (fileSuggestions.length > 0) {
            if (key.upArrow) {
                setSelectedFileIndex((prev) => prev === 0 ? fileSuggestions.length - 1 : prev - 1);
                return true;
            }
            if (key.downArrow) {
                setSelectedFileIndex((prev) => (prev + 1) % fileSuggestions.length);
                return true;
            }
            if (key.tab || key.return) {
                const selectedFile = fileSuggestions[selectedFileIndex];
                const { startPos } = extractFileReference(input);
                if (startPos >= 0) {
                    // Replace the @ reference with the selected file path
                    const beforeAt = input.slice(0, startPos);
                    const filePath = selectedFile.isDirectory
                        ? `@${selectedFile.path}/`
                        : `@${selectedFile.path}`;
                    const newInput = beforeAt + filePath + (selectedFile.isDirectory ? '' : ' ');
                    setInput(newInput);
                    setCursorPosition(newInput.length);
                    setShowFileAutocomplete(false);
                    setSelectedFileIndex(0);
                    // If it's a directory and Enter was pressed, refresh suggestions
                    if (selectedFile.isDirectory && key.return) {
                        // Keep autocomplete open for directory navigation
                        setTimeout(() => {
                            const newSuggestions = getFileSuggestions(selectedFile.path + '/');
                            setFileSuggestions(newSuggestions.slice(0, 8));
                            if (newSuggestions.length > 0) {
                                setShowFileAutocomplete(true);
                            }
                        }, 0);
                    }
                }
                return true;
            }
            if (key.escape) {
                setShowFileAutocomplete(false);
                setSelectedFileIndex(0);
                return true;
            }
        }
        return false;
    };
    const handleSpecialKey = (key) => {
        // Don't handle input if confirmation dialog is active
        if (isConfirmationActive) {
            return true; // Prevent default handling
        }
        // Handle shift+tab to toggle auto-edit mode
        if (key.shift && key.tab) {
            const newAutoEditState = !autoEditEnabled;
            setAutoEditEnabled(newAutoEditState);
            const confirmationService = ConfirmationService.getInstance();
            if (newAutoEditState) {
                // Enable auto-edit: set all operations to be accepted
                confirmationService.setSessionFlag("allOperations", true);
            }
            else {
                // Disable auto-edit: reset session flags
                confirmationService.resetSession();
            }
            return true; // Handled
        }
        // Handle escape key
        if (key.escape) {
            return handleEscapeKey(key);
        }
        // Handle command suggestions navigation
        if (showCommandSuggestions) {
            const handled = handleCommandSuggestionsNav(key);
            if (handled)
                return true;
            return handled;
        }
        // Handle model selection navigation
        if (showModelSelection) {
            const handled = handleModelSelectionNav(key);
            if (handled)
                return true;
        }
        // Handle file autocomplete navigation
        if (showFileAutocomplete) {
            const handled = handleFileAutocompleteNav(key);
            if (handled)
                return true;
        }
        return false; // Let default handling proceed
    };
    const handleInputSubmit = async (userInput) => {
        if (userInput === "exit" || userInput === "quit") {
            process.exit(0);
            return;
        }
        if (userInput.trim()) {
            // Add to persistent history (for Ctrl+R and /history command)
            const historyManager = getHistoryManager();
            historyManager.add(userInput);
            // Handle # instruction capture - save to .codebuddyrules (Claude Code-style)
            if (userInput.startsWith("#")) {
                const instruction = userInput.slice(1).trim();
                if (instruction) {
                    const result = await saveInstructionToCodeBuddyRules(instruction);
                    const entry = {
                        type: "assistant",
                        content: result,
                        timestamp: new Date(),
                    };
                    setChatHistory((prev) => [...prev, entry]);
                }
                else {
                    const helpEntry = {
                        type: "assistant",
                        content: `# Instruction Capture\n\nUsage: #<instruction>\n\nSave a project-specific instruction to .codebuddyrules.\n\nExamples:\n  # Always use TypeScript strict mode\n  # Prefer functional components over class components\n  # Use conventional commits format`,
                        timestamp: new Date(),
                    };
                    setChatHistory((prev) => [...prev, helpEntry]);
                }
                clearInput();
                return;
            }
            // Handle ! shell bypass prefix - execute command directly without AI
            if (userInput.startsWith("!")) {
                // Delegate to dispatcher for consistency?
                // Or keep direct call? Dispatcher handles it too.
                await handleDirectCommand(userInput);
                return;
            }
            // For slash commands, handleDirectCommand handles clearInput
            // For regular messages, we need to handle it here
            if (userInput.startsWith("/")) {
                await handleDirectCommand(userInput);
            }
            else {
                // Process @ file references before sending to AI
                const processedInput = await processFileReferences(userInput);
                await processUserMessage(processedInput);
            }
        }
    };
    // Removed handleShellBypass as it's now in ClientCommandDispatcher
    /**
     * Process @ file references in input
     * Replaces @path with file content or adds context about the file
     */
    const processFileReferences = async (input) => {
        // Match @path patterns (not preceded by non-whitespace, not followed by space within the reference)
        const fileRefPattern = /(?:^|(?<=\s))@([^\s@]+)/g;
        const matches = [...input.matchAll(fileRefPattern)];
        if (matches.length === 0) {
            return input;
        }
        let processedInput = input;
        const fileContents = [];
        for (const match of matches) {
            const filePath = match[1];
            const fullMatch = match[0];
            try {
                const resolvedPath = path.isAbsolute(filePath)
                    ? filePath
                    : path.resolve(process.cwd(), filePath);
                const exists = await fsPromises.access(resolvedPath).then(() => true).catch(() => false);
                if (exists) {
                    const stats = await fsPromises.stat(resolvedPath);
                    if (stats.isDirectory()) {
                        // For directories, list contents
                        const entries = await fsPromises.readdir(resolvedPath);
                        const listing = entries.slice(0, 50).join('\n');
                        fileContents.push(`📁 Directory: ${filePath}\n${listing}${entries.length > 50 ? '\n... and more files' : ''}`);
                    }
                    else if (stats.isFile()) {
                        // For files, read content (with size limit)
                        const maxSize = 100 * 1024; // 100KB limit
                        if (stats.size > maxSize) {
                            const content = (await fsPromises.readFile(resolvedPath, 'utf-8')).slice(0, maxSize);
                            fileContents.push(`📄 File: ${filePath} (truncated to 100KB)\n\`\`\`\n${content}\n\`\`\``);
                        }
                        else {
                            const content = await fsPromises.readFile(resolvedPath, 'utf-8');
                            const ext = path.extname(filePath).slice(1) || 'txt';
                            fileContents.push(`📄 File: ${filePath}\n\`\`\`${ext}\n${content}\n\`\`\``);
                        }
                    }
                    // Remove the @reference from the input text
                    processedInput = processedInput.replace(fullMatch, `[${filePath}]`);
                }
            }
            catch {
                // File doesn't exist or can't be read - leave the @reference as is
            }
        }
        if (fileContents.length > 0) {
            // Append file contents as context
            processedInput = `${processedInput}\n\n---\nReferenced files:\n\n${fileContents.join('\n\n')}`;
        }
        return processedInput;
    };
    const handleInputChange = (newInput) => {
        // Update command suggestions based on input
        if (newInput.startsWith("/")) {
            setShowCommandSuggestions(true);
            setSelectedCommandIndex(0);
            setShowFileAutocomplete(false);
        }
        else {
            setShowCommandSuggestions(false);
            setSelectedCommandIndex(0);
            // Check for @ file references
            const { found, partial } = extractFileReference(newInput);
            if (found) {
                const suggestions = getFileSuggestions(partial);
                setFileSuggestions(suggestions.slice(0, 8));
                setShowFileAutocomplete(suggestions.length > 0);
                setSelectedFileIndex(0);
            }
            else {
                setShowFileAutocomplete(false);
                setFileSuggestions([]);
            }
        }
    };
    const { input, cursorPosition, setInput, setCursorPosition, clearInput, resetHistory, handleInput, } = useEnhancedInput({
        onSubmit: handleInputSubmit,
        onSpecialKey: handleSpecialKey,
        disabled: isConfirmationActive,
    });
    // Hook up the actual input handling
    useInput((inputChar, key) => {
        handleInput(inputChar, key);
    });
    // Update command suggestions when input changes
    useEffect(() => {
        handleInputChange(input);
    }, [input]);
    // Load commands from SlashCommandManager
    const commandSuggestions = useMemo(() => {
        const slashManager = getSlashCommandManager();
        return slashManager.getCommands().map(cmd => ({
            command: `/${cmd.name}`,
            description: cmd.description
        }));
    }, []);
    // Load models from configuration with fallback to defaults
    const availableModels = useMemo(() => {
        return loadModelConfig(); // Return directly, interface already matches
    }, []);
    const handleDirectCommand = async (input) => {
        const context = {
            agent,
            chatHistory,
            setChatHistory,
            setIsProcessing,
            setIsStreaming,
            setTokenCount,
            setProcessingTime,
            processingStartTime,
            setInput,
            clearInput,
            resetHistory,
            setShowModelSelection,
            setSelectedModelIndex,
            availableModels,
            processUserMessage: processUserMessage // Using the function defined below
        };
        return await ClientCommandDispatcher.dispatch(input, context);
    };
    const processUserMessage = async (userInput) => {
        const userEntry = {
            type: "user",
            content: userInput,
            timestamp: new Date(),
        };
        setChatHistory((prev) => [...prev, userEntry]);
        // Log user message to interaction logger
        try {
            const il = getInteractionLogger();
            if (il.getCurrentSessionId()) {
                il.logMessage({ role: 'user', content: userInput });
            }
        }
        catch (_err) { }
        setIsProcessing(true);
        clearInput();
        try {
            setIsStreaming(true);
            let streamingEntry = null;
            let fullResponseContent = "";
            for await (const chunk of agent.processUserMessageStream(userInput)) {
                switch (chunk.type) {
                    case "content":
                        if (chunk.content) {
                            fullResponseContent += chunk.content;
                            if (!streamingEntry) {
                                const newStreamingEntry = {
                                    type: "assistant",
                                    content: chunk.content,
                                    timestamp: new Date(),
                                    isStreaming: true,
                                };
                                setChatHistory((prev) => [...prev, newStreamingEntry]);
                                streamingEntry = newStreamingEntry;
                            }
                            else {
                                setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
                                    ? { ...entry, content: entry.content + chunk.content }
                                    : entry));
                            }
                        }
                        break;
                    case "token_count":
                        if (chunk.tokenCount !== undefined) {
                            setTokenCount(chunk.tokenCount);
                        }
                        break;
                    case "tool_calls":
                        if (chunk.toolCalls) {
                            // Stop streaming for the current assistant message
                            setChatHistory((prev) => prev.map((entry) => entry.isStreaming
                                ? {
                                    ...entry,
                                    isStreaming: false,
                                    toolCalls: chunk.toolCalls,
                                }
                                : entry));
                            streamingEntry = null;
                            // Log tool calls to interaction logger
                            try {
                                const il = getInteractionLogger();
                                if (il.getCurrentSessionId()) {
                                    il.logToolCalls(chunk.toolCalls.map(tc => ({
                                        id: tc.id,
                                        name: tc.function.name,
                                        arguments: JSON.parse(tc.function.arguments || '{}'),
                                    })));
                                }
                            }
                            catch (_err) { }
                            // Add individual tool call entries to show tools are being executed
                            chunk.toolCalls.forEach((toolCall) => {
                                const toolCallEntry = {
                                    type: "tool_call",
                                    content: "Executing...",
                                    timestamp: new Date(),
                                    toolCall: toolCall,
                                };
                                setChatHistory((prev) => [...prev, toolCallEntry]);
                            });
                        }
                        break;
                    case "tool_result":
                        if (chunk.toolCall && chunk.toolResult) {
                            // Log tool result to interaction logger
                            try {
                                const il = getInteractionLogger();
                                if (il.getCurrentSessionId()) {
                                    il.logToolResult(chunk.toolCall.id, {
                                        success: chunk.toolResult.success,
                                        output: chunk.toolResult.output?.substring(0, 500),
                                        error: chunk.toolResult.error,
                                    });
                                }
                            }
                            catch (_err) { }
                            setChatHistory((prev) => prev.map((entry) => {
                                if (entry.isStreaming) {
                                    return { ...entry, isStreaming: false };
                                }
                                // Update the existing tool_call entry with the result
                                if (entry.type === "tool_call" &&
                                    entry.toolCall?.id === chunk.toolCall?.id) {
                                    return {
                                        ...entry,
                                        type: "tool_result",
                                        content: chunk.toolResult?.success
                                            ? chunk.toolResult?.output || "Success"
                                            : chunk.toolResult?.error || "Error occurred",
                                        toolResult: chunk.toolResult,
                                    };
                                }
                                return entry;
                            }));
                            streamingEntry = null;
                        }
                        break;
                    case "done":
                        if (streamingEntry) {
                            setChatHistory((prev) => prev.map((entry) => entry.isStreaming ? { ...entry, isStreaming: false } : entry));
                        }
                        setIsStreaming(false);
                        // Log assistant response to interaction logger
                        if (fullResponseContent) {
                            try {
                                const il = getInteractionLogger();
                                if (il.getCurrentSessionId()) {
                                    il.logMessage({ role: 'assistant', content: fullResponseContent });
                                }
                            }
                            catch (_err) { }
                        }
                        // Auto-speak the response if enabled
                        const ttsManager = getTTSManager();
                        if (ttsManager.getConfig().autoSpeak && fullResponseContent.trim()) {
                            // Strip markdown formatting for cleaner speech
                            const textToSpeak = fullResponseContent
                                .replace(/```[\s\S]*?```/g, '') // Remove code blocks
                                .replace(/`[^`]+`/g, '') // Remove inline code
                                .replace(/\*\*([^*]+)\*\*/g, '$1') // Bold to plain
                                .replace(/\*([^*]+)\*/g, '$1') // Italic to plain
                                .replace(/#+\s/g, '') // Remove headers
                                .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Links to text
                                .replace(/\n+/g, '. ') // Newlines to pauses
                                .trim();
                            if (textToSpeak) {
                                // Fire-and-forget with error handling
                                ttsManager.speak(textToSpeak, 'fr').catch(() => {
                                    // Errors are emitted via 'error' event, no need to handle here
                                });
                            }
                        }
                        break;
                }
            }
        }
        catch (error) {
            const errorEntry = {
                type: "assistant",
                content: `Error: ${getErrorMessage(error)}`,
                timestamp: new Date(),
            };
            setChatHistory((prev) => [...prev, errorEntry]);
            setIsStreaming(false);
        }
        setIsProcessing(false);
        processingStartTime.current = 0;
    };
    return {
        input,
        cursorPosition,
        showCommandSuggestions,
        selectedCommandIndex,
        showModelSelection,
        selectedModelIndex,
        showFileAutocomplete,
        selectedFileIndex,
        fileSuggestions,
        commandSuggestions,
        availableModels,
        agent,
        autoEditEnabled,
    };
}
//# sourceMappingURL=use-input-handler.js.map