import { clampSessionTimerDurationMs, clampSessionTimerLayout, createInitialSessionTimerState, getSessionTimerRemainingMs, type SessionTimerEvent, type SessionTimerState, } from '../../shared/types/sessionTimer'; export class TimerSessionStore { private state: SessionTimerState = createInitialSessionTimerState(); getState(): SessionTimerState { return this.state; } clear(): SessionTimerState { this.state = createInitialSessionTimerState(); return this.state; } dispatch(event: SessionTimerEvent, now = Date.now()): SessionTimerState { switch (event.kind) { case 'setDuration': { const durationMs = clampSessionTimerDurationMs(event.durationMs); if (this.state.running) { const remaining = getSessionTimerRemainingMs(this.state, now); this.state = { ...this.state, revision: this.state.revision + 1, durationMs, remainingMs: Math.min(remaining, durationMs), endsAt: now + Math.min(remaining, durationMs), }; } else { this.state = { ...this.state, revision: this.state.revision + 1, durationMs, remainingMs: durationMs, endsAt: null, running: false, }; } return this.state; } case 'start': { const remaining = getSessionTimerRemainingMs(this.state, now); if (remaining <= 0) { // Restart from full duration if already at zero. const durationMs = this.state.durationMs; this.state = { ...this.state, revision: this.state.revision + 1, remainingMs: durationMs, running: true, endsAt: now + durationMs, }; return this.state; } this.state = { ...this.state, revision: this.state.revision + 1, running: true, endsAt: now + remaining, remainingMs: remaining, }; return this.state; } case 'pause': { if (!this.state.running) return this.state; const remaining = getSessionTimerRemainingMs(this.state, now); this.state = { ...this.state, revision: this.state.revision + 1, running: false, endsAt: null, remainingMs: remaining, }; return this.state; } case 'reset': { this.state = { ...this.state, revision: this.state.revision + 1, running: false, endsAt: null, remainingMs: this.state.durationMs, }; return this.state; } case 'setVisibleToPlayers': { if (this.state.visibleToPlayers === event.visible) return this.state; this.state = { ...this.state, revision: this.state.revision + 1, visibleToPlayers: event.visible, }; return this.state; } case 'layout.set': { const layout = clampSessionTimerLayout(event.layout); this.state = { ...this.state, revision: this.state.revision + 1, layout, }; return this.state; } default: { const _exhaustive: never = event; return _exhaustive; } } } /** Когда running и время вышло — зафиксировать 00:00 (вызывается из UI/main по желанию). */ settleIfExpired(now = Date.now()): SessionTimerState { if (!this.state.running) return this.state; const remaining = getSessionTimerRemainingMs(this.state, now); if (remaining > 0) return this.state; this.state = { ...this.state, revision: this.state.revision + 1, running: false, endsAt: null, remainingMs: 0, }; return this.state; } /** Слепок: таймер на паузе, без абсолютного момента конца. */ loadPaused(next: Pick): SessionTimerState { const durationMs = clampSessionTimerDurationMs(next.durationMs); const remainingMs = Math.max(0, Math.min(durationMs, Math.round(next.remainingMs))); this.state = { revision: this.state.revision + 1, durationMs, remainingMs, running: false, endsAt: null, visibleToPlayers: Boolean(next.visibleToPlayers), layout: clampSessionTimerLayout(next.layout), }; return this.state; } }