import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { appendFloatingText, groupFloatingTextsByMember, stripFloatingTextSide, type BasicFloatingCombatText, } from '../combat/combatPresentation' type FloatingSide = 'player' | 'cpu' type SideFloatingCombatText = BasicFloatingCombatText & { side: FloatingSide } type FloatingCombatTextOptions = { durationMs?: number } const FLOATING_TEXT_CLEANUP_INTERVAL_MS = 100 export function useFloatingCombatText({ durationMs = 900, }: FloatingCombatTextOptions = {}) { const [floatingTexts, setFloatingTexts] = useState([]) const nextId = useRef(1) const expirationsRef = useRef(new Map()) const clearFloatingTexts = useCallback(() => { expirationsRef.current.clear() setFloatingTexts([]) }, []) const addFloatingText = useCallback((entry: Omit) => { if (entry.value <= 0) return const id = nextId.current++ expirationsRef.current.set(id, performance.now() + durationMs) setFloatingTexts((current) => appendFloatingText(current, { ...entry, id } as TText)) }, [durationMs]) useEffect(() => { const timer = window.setInterval(() => { const now = performance.now() let removed = false for (const [id, expiresAt] of expirationsRef.current) { if (expiresAt > now) continue expirationsRef.current.delete(id) removed = true } if (!removed) return setFloatingTexts((current) => current.filter((text) => expirationsRef.current.has(text.id))) }, FLOATING_TEXT_CLEANUP_INTERVAL_MS) return () => window.clearInterval(timer) }, []) const floatingTextsByMember = useMemo( () => groupFloatingTextsByMember(floatingTexts), [floatingTexts], ) return { floatingTexts, floatingTextsByMember, addFloatingText, clearFloatingTexts, } } export function useSidedFloatingCombatText(options?: FloatingCombatTextOptions) { const { floatingTexts, addFloatingText, clearFloatingTexts, } = useFloatingCombatText(options) const playerFloatingTexts = useMemo( () => floatingTexts.filter((entry) => entry.side === 'player'), [floatingTexts], ) const cpuFloatingTexts = useMemo( () => floatingTexts.filter((entry) => entry.side === 'cpu'), [floatingTexts], ) const playerFloatingTextsByMember = useMemo( () => groupFloatingTextsByMember(playerFloatingTexts), [playerFloatingTexts], ) const cpuFloatingTextsByMember = useMemo( () => groupFloatingTextsByMember(cpuFloatingTexts), [cpuFloatingTexts], ) const dualScreenFloatingTexts = useMemo( () => stripFloatingTextSide(playerFloatingTexts), [playerFloatingTexts], ) return { floatingTexts, playerFloatingTexts, cpuFloatingTexts, playerFloatingTextsByMember, cpuFloatingTextsByMember, dualScreenFloatingTexts, addFloatingText, clearFloatingTexts, } }