| 1 | import { useState, useCallback } from 'react'; |
| 2 | import { gameApi, CommandResponse, TreeNode } from '@/lib/api'; |
| 3 | |
| 4 | interface CommandExecutionState { |
| 5 | executing: boolean; |
| 6 | commandHistory: CommandHistoryEntry[]; |
| 7 | } |
| 8 | |
| 9 | interface CommandHistoryEntry { |
| 10 | command: string; |
| 11 | output: string; |
| 12 | success: boolean; |
| 13 | } |
| 14 | |
| 15 | export const useCommandExecution = ( |
| 16 | gameTreeId: number | null, |
| 17 | sessionId: number | null, |
| 18 | onLocationChange?: (newPath: string) => void, |
| 19 | onMoleKilled?: (response: CommandResponse) => void, |
| 20 | onTreeUpdate?: (updater: (tree: TreeNode) => TreeNode) => void |
| 21 | ) => { |
| 22 | const [state, setState] = useState<CommandExecutionState>({ |
| 23 | executing: false, |
| 24 | commandHistory: [], |
| 25 | }); |
| 26 | |
| 27 | const addToHistory = useCallback((entry: CommandHistoryEntry) => { |
| 28 | setState(prev => ({ |
| 29 | ...prev, |
| 30 | commandHistory: [...prev.commandHistory, entry], |
| 31 | })); |
| 32 | }, []); |
| 33 | |
| 34 | const clearHistory = useCallback(() => { |
| 35 | setState(prev => ({ |
| 36 | ...prev, |
| 37 | commandHistory: [], |
| 38 | })); |
| 39 | }, []); |
| 40 | |
| 41 | const executeCommand = useCallback(async (cmd: string) => { |
| 42 | if (!gameTreeId || !cmd.trim() || state.executing) return; |
| 43 | |
| 44 | setState(prev => ({ ...prev, executing: true })); |
| 45 | |
| 46 | try { |
| 47 | const response = await gameApi.executeCommand( |
| 48 | gameTreeId, |
| 49 | cmd, |
| 50 | sessionId || undefined |
| 51 | ); |
| 52 | |
| 53 | // Build output with timer warnings |
| 54 | let fullOutput = response.output; |
| 55 | |
| 56 | // Add timer warnings if present |
| 57 | if (response.timer_warnings && response.timer_warnings.length > 0) { |
| 58 | const warnings = response.timer_warnings.map(w => |
| 59 | `⚠️ ${w.level}: ${w.message}` |
| 60 | ).join('\n'); |
| 61 | fullOutput = warnings + (fullOutput ? '\n' + fullOutput : ''); |
| 62 | } |
| 63 | |
| 64 | // Add to command history |
| 65 | addToHistory({ |
| 66 | command: cmd, |
| 67 | output: fullOutput, |
| 68 | success: response.success, |
| 69 | }); |
| 70 | |
| 71 | // Handle location change |
| 72 | if (response.current_path && onLocationChange) { |
| 73 | onLocationChange(response.current_path); |
| 74 | } |
| 75 | |
| 76 | // Handle mole spawning |
| 77 | if (response.mole_spawned && onMoleKilled) { |
| 78 | // Format the output to include timer info on new line |
| 79 | if (response.timer_reason && !response.output.includes('New mole detected')) { |
| 80 | response.output += `\nNew mole detected ${response.timer_reason}!`; |
| 81 | } |
| 82 | onMoleKilled(response); |
| 83 | } |
| 84 | |
| 85 | // Legacy: Handle game won |
| 86 | if (response.game_won && !response.mole_spawned && onTreeUpdate) { |
| 87 | onTreeUpdate((tree) => ({ |
| 88 | ...tree, |
| 89 | has_mole: true, |
| 90 | })); |
| 91 | } |
| 92 | |
| 93 | return response; |
| 94 | } catch { |
| 95 | addToHistory({ |
| 96 | command: cmd, |
| 97 | output: 'Error: Failed to execute command. Check your connection.', |
| 98 | success: false, |
| 99 | }); |
| 100 | return null; |
| 101 | } finally { |
| 102 | setState(prev => ({ ...prev, executing: false })); |
| 103 | } |
| 104 | }, [gameTreeId, sessionId, state.executing, addToHistory, onLocationChange, onMoleKilled, onTreeUpdate]); |
| 105 | |
| 106 | return { |
| 107 | executing: state.executing, |
| 108 | commandHistory: state.commandHistory, |
| 109 | executeCommand, |
| 110 | addToHistory, |
| 111 | clearHistory, |
| 112 | }; |
| 113 | }; |