import type { DomainEvent } from "./events.js"; import type { CombatantId, DomainError, Encounter } from "./types.js"; export interface EditCombatantSuccess { readonly encounter: Encounter; readonly events: DomainEvent[]; } /** * Pure function that renames a combatant in an encounter by ID. * * FR-001: Accepts Encounter, CombatantId, and newName; returns next state + events. * FR-002: Emits a CombatantUpdated event with combatantId, oldName, newName. * FR-004: Rejects empty/whitespace-only names with DomainError. * FR-005: Preserves activeIndex and roundNumber. * FR-006: Preserves combatant list order. */ export function editCombatant( encounter: Encounter, id: CombatantId, newName: string, ): EditCombatantSuccess | DomainError { const trimmed = newName.trim(); if (trimmed === "") { return { kind: "domain-error", code: "invalid-name", message: "Combatant name must not be empty", }; } const index = encounter.combatants.findIndex((c) => c.id === id); if (index === -1) { return { kind: "domain-error", code: "combatant-not-found", message: `No combatant found with ID "${id}"`, }; } const oldName = encounter.combatants[index].name; return { encounter: { combatants: encounter.combatants.map((c) => c.id === id ? { ...c, name: trimmed } : c, ), activeIndex: encounter.activeIndex, roundNumber: encounter.roundNumber, }, events: [ { type: "CombatantUpdated", combatantId: id, oldName, newName: trimmed, }, ], }; }