import { useState, useEffect } from 'react'; import type { Coord, CursorManager, CursorMode } from './types'; import type { GameStatusManager } from '../useGameStatus/types'; import { useVimMotions } from './useVimMotions'; export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject) { const [position, setPosition] = useState({ row: 0, col: 0 }); const [mode, setMode] = useState('normal'); const { gameStatus } = gameStatusManager; const vimMotions = useVimMotions(); function flashInvalidMove(): void { const container = containerRef.current; if (!container) return; const cells = container.querySelectorAll('.sudoku-cell'); const index = position.row * 9 + position.col; const targetCell = cells[index]; if (targetCell) { targetCell.classList.add('invalid-move'); setTimeout(() => { targetCell.classList.remove('invalid-move'); }, 500); } } function move(dRows: number, dCols: number, count: number = 1): void { setPosition(currentPos => { const newRow = currentPos.row + (dRows * count); const newCol = currentPos.col + (dCols * count); if (newRow < 0 || newRow > 8 || newCol < 0 || newCol > 8) { flashInvalidMove(); return currentPos; } return { row: newRow, col: newCol }; }); } function moveLeft(count = 1): void { move(0, -1, count); } function moveRight(count = 1): void { move(0, 1, count); } function moveUp(count = 1): void { move(-1, 0, count); } function moveDown(count = 1): void { move(1, 0, count); } function moveToStart(): void { setPosition(prev => ({ ...prev, col: 0 })); } function moveToEnd(): void { setPosition(prev => ({ ...prev, col: 8 })); } function moveToTop(): void { setPosition(prev => ({ row: 0, col: prev.col })); } function moveToBottom(): void { setPosition(prev => ({ row: 8, col: prev.col })); } function repeatLastMotion(): void { const motion = vimMotions.repeatLastMotion(); if (!motion) return; setPosition(currentPos => { const newRow = currentPos.row + (motion.dr * motion.steps); const newCol = currentPos.col + (motion.dc * motion.steps); if (newRow < 0 || newRow > 8 || newCol < 0 || newCol > 8) { flashInvalidMove(); return currentPos; } return { row: newRow, col: newCol }; }); } useEffect(() => { if (gameStatus !== 'started') return; function handleKeyDown(e: KeyboardEvent) { const key = e.key; if (mode === 'edit') { if (key === 'Escape') { setMode('normal'); } return; } if (key === 'i') { setMode('edit'); return; } const result = vimMotions.processKey(key); if (!result) return; switch (result.type) { case 'move': move(result.data.dr, result.data.dc, result.data.steps); break; case 'anchor': switch (result.data) { case 'start': moveToStart(); break; case 'end': moveToEnd(); break; case 'top': moveToTop(); break; case 'bottom': moveToBottom(); break; } break; case 'repeat': if (result.data) { move(result.data.dr, result.data.dc, result.data.steps); } break; case 'count': break; } } window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [gameStatus, mode]); const cursor: CursorManager = { position: () => position, mode: () => mode, moveLeft, moveRight, moveUp, moveDown, moveToStart, moveToEnd, moveToTop, moveToBottom, repeatLastMotion, }; return cursor; }