All files / web/src/arcade-games/constant-explorer Provider.tsx

0% Statements 0/114
0% Branches 0/1
0% Functions 0/1
0% Lines 0/114

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                                                                                                                                                                                                                                     
'use client'

import { createContext, useContext, useMemo } from 'react'
import type { ReactNode } from 'react'
import { CONSTANT_IDS } from '@/components/toys/number-line/talkToNumber/explorationRegistry'
import { useGameMode } from '@/contexts/GameModeContext'
import { usePlayerGameHistory } from '@/hooks/useGameResults'

/** All constant IDs that have demos + narration available */
const DEMO_CONSTANT_IDS = [...CONSTANT_IDS]

function pickRandomConstant(): string {
  return DEMO_CONSTANT_IDS[Math.floor(Math.random() * DEMO_CONSTANT_IDS.length)]
}

/**
 * Pick a constant the student has watched least.
 * Counts plays per constant from game history, then picks randomly
 * from the set with the lowest count (including 0 for never-watched).
 */
function pickBalancedConstant(
  history: Array<{ fullReport?: { leaderboardEntry?: { difficulty?: string } } | null }> | undefined
): string {
  // Count how many times each constant has been watched
  const counts = new Map<string, number>()
  for (const id of DEMO_CONSTANT_IDS) {
    counts.set(id, 0)
  }

  if (history) {
    for (const entry of history) {
      const constantId = entry.fullReport?.leaderboardEntry?.difficulty
      if (constantId && counts.has(constantId)) {
        counts.set(constantId, (counts.get(constantId) ?? 0) + 1)
      }
    }
  }

  // Find the minimum count
  let minCount = Infinity
  for (const count of counts.values()) {
    if (count < minCount) minCount = count
  }

  // Collect all constants with that minimum count
  const leastWatched: string[] = []
  for (const [id, count] of counts) {
    if (count === minCount) leastWatched.push(id)
  }

  // Pick randomly from the least-watched set
  return leastWatched[Math.floor(Math.random() * leastWatched.length)]
}

function resolveConstantId(
  raw: unknown,
  history: Array<{ fullReport?: { leaderboardEntry?: { difficulty?: string } } | null }> | undefined
): string {
  if (typeof raw === 'string') {
    if (raw === 'balance') return pickBalancedConstant(history)
    if (raw !== 'random' && CONSTANT_IDS.has(raw)) return raw
  }
  return pickRandomConstant()
}

const ConstantExplorerContext = createContext<{ constantId: string }>({
  constantId: 'pi',
})

export function useConstantExplorerConfig() {
  return useContext(ConstantExplorerContext)
}

/**
 * Provider for the constant-explorer "game".
 *
 * Reads constantId from roomData.gameConfig (set by the teacher in the
 * start-practice modal), resolving 'random'/'balance' to a concrete constant.
 * For 'balance', queries the player's game history to find least-watched constants.
 */
export function ConstantExplorerProvider({ children }: { children: ReactNode }) {
  const { getActivePlayers, roomData } = useGameMode()

  // Get player ID for history query
  const playerId = useMemo(() => {
    const players = getActivePlayers()
    return players[0]?.id ?? null
  }, [getActivePlayers])

  // Query this player's constant-explorer history (for balance mode)
  const { data: historyData } = usePlayerGameHistory(playerId, {
    gameName: 'constant-explorer',
    limit: 200, // Enough to cover many sessions
  })

  const value = useMemo(() => {
    const gameConfig = roomData?.gameConfig as Record<string, unknown> | null | undefined
    const explorerConfig = gameConfig?.['constant-explorer'] as Record<string, unknown> | undefined
    const constantId = resolveConstantId(
      explorerConfig?.constantId,
      historyData?.history as
        | Array<{
            fullReport?: { leaderboardEntry?: { difficulty?: string } } | null
          }>
        | undefined
    )
    return { constantId }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- resolve once when history loads
  }, [roomData?.gameConfig, historyData])

  return (
    <ConstantExplorerContext.Provider value={value}>{children}</ConstantExplorerContext.Provider>
  )
}