import { clampNpcTokenSessionScale, DEFAULT_NPC_TOKEN_SESSION_SCALE, type SceneNpcTokensSessionEvent, type SceneNpcTokensSessionState, } from '../../shared/types/appPlayers'; function emptyState(revision = 1): SceneNpcTokensSessionState { return { revision, byPlacementId: {}, scale: DEFAULT_NPC_TOKEN_SESSION_SCALE, }; } export class SceneNpcTokensSessionStore { private state: SceneNpcTokensSessionState = emptyState(); getState(): SceneNpcTokensSessionState { return this.state; } reset(): SceneNpcTokensSessionState { if ( Object.keys(this.state.byPlacementId).length === 0 && this.state.scale === DEFAULT_NPC_TOKEN_SESSION_SCALE ) { return this.state; } this.state = emptyState(this.state.revision + 1); return this.state; } dispatch(event: SceneNpcTokensSessionEvent): SceneNpcTokensSessionState { switch (event.kind) { case 'clear': return this.reset(); case 'setScale': { const scale = clampNpcTokenSessionScale(event.scale); if (this.state.scale === scale) return this.state; this.state = { ...this.state, revision: this.state.revision + 1, scale, }; return this.state; } case 'move': { const placementId = String(event.placementId ?? ''); if (!placementId) return this.state; const nx = Math.max(0, Math.min(1, event.nx)); const ny = Math.max(0, Math.min(1, event.ny)); if (!Number.isFinite(nx) || !Number.isFinite(ny)) return this.state; const prev = this.state.byPlacementId[placementId]; if (prev && prev.nx === nx && prev.ny === ny) return this.state; this.state = { revision: this.state.revision + 1, byPlacementId: { ...this.state.byPlacementId, [placementId]: { nx, ny }, }, scale: this.state.scale, }; return this.state; } default: { const _exhaustive: never = event; void _exhaustive; return this.state; } } } }