import type { EncounterStore } from "@initiative/application"; import { addCombatantUseCase, adjustHpUseCase, advanceTurnUseCase, editCombatantUseCase, removeCombatantUseCase, setHpUseCase, setInitiativeUseCase, } from "@initiative/application"; import type { CombatantId, DomainEvent, Encounter } from "@initiative/domain"; import { combatantId, createEncounter, isDomainError, } from "@initiative/domain"; import { useCallback, useEffect, useRef, useState } from "react"; import { loadEncounter, saveEncounter, } from "../persistence/encounter-storage.js"; function createDemoEncounter(): Encounter { const result = createEncounter([ { id: combatantId("1"), name: "Aria" }, { id: combatantId("2"), name: "Brak" }, { id: combatantId("3"), name: "Cael" }, ]); if (isDomainError(result)) { throw new Error(`Failed to create demo encounter: ${result.message}`); } return result; } function initializeEncounter(): Encounter { const stored = loadEncounter(); if (stored !== null) return stored; return createDemoEncounter(); } function deriveNextId(encounter: Encounter): number { let max = 0; for (const c of encounter.combatants) { const match = /^c-(\d+)$/.exec(c.id); if (match) { const n = Number.parseInt(match[1], 10); if (n > max) max = n; } } return max; } export function useEncounter() { const [encounter, setEncounter] = useState(initializeEncounter); const [events, setEvents] = useState([]); const encounterRef = useRef(encounter); encounterRef.current = encounter; useEffect(() => { saveEncounter(encounter); }, [encounter]); const makeStore = useCallback((): EncounterStore => { return { get: () => encounterRef.current, save: (e) => setEncounter(e), }; }, []); const advanceTurn = useCallback(() => { const result = advanceTurnUseCase(makeStore()); if (isDomainError(result)) { return; } setEvents((prev) => [...prev, ...result]); }, [makeStore]); const nextId = useRef(deriveNextId(encounter)); const addCombatant = useCallback( (name: string) => { const id = combatantId(`c-${++nextId.current}`); const result = addCombatantUseCase(makeStore(), id, name); if (isDomainError(result)) { return; } setEvents((prev) => [...prev, ...result]); }, [makeStore], ); const removeCombatant = useCallback( (id: CombatantId) => { const result = removeCombatantUseCase(makeStore(), id); if (isDomainError(result)) { return; } setEvents((prev) => [...prev, ...result]); }, [makeStore], ); const editCombatant = useCallback( (id: CombatantId, newName: string) => { const result = editCombatantUseCase(makeStore(), id, newName); if (isDomainError(result)) { return; } setEvents((prev) => [...prev, ...result]); }, [makeStore], ); const setInitiative = useCallback( (id: CombatantId, value: number | undefined) => { const result = setInitiativeUseCase(makeStore(), id, value); if (isDomainError(result)) { return; } setEvents((prev) => [...prev, ...result]); }, [makeStore], ); const setHp = useCallback( (id: CombatantId, maxHp: number | undefined) => { const result = setHpUseCase(makeStore(), id, maxHp); if (isDomainError(result)) { return; } setEvents((prev) => [...prev, ...result]); }, [makeStore], ); const adjustHp = useCallback( (id: CombatantId, delta: number) => { const result = adjustHpUseCase(makeStore(), id, delta); if (isDomainError(result)) { return; } setEvents((prev) => [...prev, ...result]); }, [makeStore], ); return { encounter, events, advanceTurn, addCombatant, removeCombatant, editCombatant, setInitiative, setHp, adjustHp, } as const; }