All files / web/src/arcade-games/know-your-world/features/letter-confirmation useLetterConfirmation.ts

31.13% Statements 52/167
100% Branches 0/0
0% Functions 0/1
31.13% Lines 52/167

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 1681x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                                                                                                                                        
/**
 * Letter Confirmation Hook
 *
 * Manages the letter confirmation state and logic for Learning mode.
 * This hook handles:
 * - Tracking confirmation progress
 * - Keyboard event handling
 * - Turn-based mode restrictions
 * - Progress calculation
 */
 
'use client'
 
import { useCallback, useEffect, useMemo, useRef } from 'react'
import type {
  UseLetterConfirmationOptions,
  UseLetterConfirmationReturn,
  LetterStatus,
} from './types'
import {
  getNthNonSpaceLetter,
  normalizeToBaseLetter,
  getLetterStatus as getLetterStatusUtil,
  calculateProgress,
} from './letterUtils'
 
/**
 * Hook for managing letter confirmation in Learning mode.
 *
 * This hook provides:
 * - Keyboard event handling for letter input
 * - Progress tracking and completion detection
 * - Turn-based mode enforcement
 * - Letter status calculation for display
 *
 * @example
 * ```tsx
 * const confirmation = useLetterConfirmation({
 *   regionName: 'France',
 *   requiredLetters: 3,
 *   confirmedCount: state.nameConfirmationProgress,
 *   isMyTurn: true,
 *   gameMode: 'cooperative',
 *   onConfirmLetter: (letter, index) => dispatch({ type: 'CONFIRM_LETTER', ... }),
 * })
 *
 * if (confirmation.isComplete) {
 *   // Show "Now find it on the map"
 * }
 * ```
 */
export function useLetterConfirmation({
  regionName,
  requiredLetters,
  confirmedCount,
  isMyTurn,
  gameMode,
  onConfirmLetter,
  onNotYourTurn,
}: UseLetterConfirmationOptions): UseLetterConfirmationReturn {
  // Optimistic letter count ref - prevents race conditions when typing fast
  const optimisticCountRef = useRef(confirmedCount)

  // Sync optimistic count when server state updates
  useEffect(() => {
    optimisticCountRef.current = confirmedCount
  }, [confirmedCount])

  // Reset optimistic count when region changes
  useEffect(() => {
    optimisticCountRef.current = 0
  }, [regionName])

  // Calculate derived state
  const isRequired = requiredLetters > 0
  const isComplete = confirmedCount >= requiredLetters

  // Get the next expected letter
  const nextExpectedLetter = useMemo(() => {
    if (!regionName || isComplete) return null
    const letterInfo = getNthNonSpaceLetter(regionName, confirmedCount)
    return letterInfo ? normalizeToBaseLetter(letterInfo.char) : null
  }, [regionName, confirmedCount, isComplete])

  // Calculate progress (0-1)
  const progress = useMemo(
    () => calculateProgress(confirmedCount, requiredLetters),
    [confirmedCount, requiredLetters]
  )

  // Get letter status for display
  const getLetterStatus = useCallback(
    (nonSpaceIndex: number): LetterStatus => {
      return getLetterStatusUtil(nonSpaceIndex, confirmedCount, requiredLetters, isComplete)
    },
    [confirmedCount, requiredLetters, isComplete]
  )

  // Handle keyboard input
  useEffect(() => {
    if (!isRequired || isComplete || !regionName) {
      return
    }

    const handleKeyDown = (e: KeyboardEvent) => {
      // Ignore if typing in an input or textarea
      if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
        return
      }

      // Only accept single character keys (letters only)
      const pressedLetter = e.key.toLowerCase()
      if (pressedLetter.length !== 1 || !/[a-z]/i.test(pressedLetter)) {
        return
      }

      // In turn-based mode, only allow the current player to type
      if (gameMode === 'turn-based' && !isMyTurn) {
        onNotYourTurn?.()
        return
      }

      // Use optimistic count to prevent race conditions when typing fast
      const nextLetterIndex = optimisticCountRef.current
      if (nextLetterIndex >= requiredLetters) {
        return // Already confirmed all required letters
      }

      // Get the nth non-space letter (skipping spaces in the name)
      const letterInfo = getNthNonSpaceLetter(regionName, nextLetterIndex)
      if (!letterInfo) {
        return // No more letters to confirm
      }

      // Normalize accented letters to base ASCII
      const expectedLetter = normalizeToBaseLetter(letterInfo.char)

      if (pressedLetter === expectedLetter) {
        // Optimistically advance count before server responds
        optimisticCountRef.current = nextLetterIndex + 1
        // Dispatch to shared state
        onConfirmLetter(pressedLetter, nextLetterIndex)
      }
      // Ignore wrong characters silently
    }

    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [
    isRequired,
    isComplete,
    regionName,
    requiredLetters,
    isMyTurn,
    gameMode,
    onConfirmLetter,
    onNotYourTurn,
  ])

  return {
    isComplete,
    nextExpectedLetter,
    progress,
    isRequired,
    getLetterStatus,
  }
}