import type { SceneDarknessEvent, SceneDarknessRevealStroke, SceneDarknessState } from '../../shared/types'; function emptyState(): SceneDarknessState { return { revision: 1, enabled: false, cacheKey: null, strokes: [], draft: null, }; } export class SceneDarknessStore { private state: SceneDarknessState = emptyState(); /** Кэш раскрытых областей по ключу (graphNodeId или sceneId) на время сессии показа. */ private cache = new Map(); private currentKey: string | null = null; getState(): SceneDarknessState { return { ...this.state, strokes: [...this.state.strokes] }; } /** Сброс кэша при новом запуске показа (кнопка «Запустить»). */ resetSession(): void { this.cache.clear(); this.currentKey = null; this.state = emptyState(); } /** * Переключение сцены: сохраняем текущие штрихи в кэш и загружаем состояние новой сцены. * Если ключа ещё не было в сессии — берём seed (база из файла партии). */ switchScene( cacheKey: string | null, enabled: boolean, seedStrokes: readonly SceneDarknessRevealStroke[] = [], ): SceneDarknessState { if (this.currentKey !== null) { this.cache.set(this.currentKey, this.state.strokes.map(cloneStroke)); } this.currentKey = cacheKey; let strokes: SceneDarknessRevealStroke[] = []; if (cacheKey && enabled) { if (this.cache.has(cacheKey)) { strokes = (this.cache.get(cacheKey) ?? []).map(cloneStroke); } else { strokes = seedStrokes.map(cloneStroke); this.cache.set(cacheKey, strokes.map(cloneStroke)); } } this.state = { revision: this.state.revision + 1, enabled, cacheKey, strokes, draft: null, }; return this.getState(); } dispatch(event: SceneDarknessEvent): SceneDarknessState { if (!this.state.enabled) return this.getState(); switch (event.kind) { case 'draft.set': this.state = { ...this.state, revision: this.state.revision + 1, draft: event.draft, }; return this.getState(); case 'stroke.add': { const strokes = [...this.state.strokes, event.stroke]; if (this.currentKey) { this.cache.set(this.currentKey, strokes); } this.state = { ...this.state, revision: this.state.revision + 1, strokes, draft: null, }; return this.getState(); } default: { // eslint-disable-next-line @typescript-eslint/no-unused-vars const _x: never = event; return this.getState(); } } } /** Кэш по карточкам, включая текущую. Пустые карточки не входят. */ exportByCard(): Record { if (this.currentKey !== null) { this.cache.set(this.currentKey, this.state.strokes.map(cloneStroke)); } const out: Record = {}; for (const [key, strokes] of this.cache) { if (strokes.length === 0) continue; out[key] = strokes.map(cloneStroke); } return out; } /** Кладёт кэш, не переключая карточку. Дальше вызывают switchScene. */ importByCard(byCard: Record): void { this.cache.clear(); this.currentKey = null; this.state = emptyState(); for (const [key, strokes] of Object.entries(byCard)) { if (!Array.isArray(strokes) || strokes.length === 0) continue; this.cache.set(key, strokes.map(cloneStroke)); } } } function cloneStroke(stroke: SceneDarknessRevealStroke): SceneDarknessRevealStroke { return { ...stroke, points: stroke.points.map((p) => ({ ...p })) }; }