import type { Config, Selections } from "./types"; type Listener = () => void; /** * Minimal reactive store: holds the immutable config plus mutable user * selections, and notifies subscribers on any change. */ export class Store { private listeners = new Set(); constructor( public readonly config: Config, private selections: Selections ) {} getSelections(): Selections { return this.selections; } onChange(listener: Listener): void { this.listeners.add(listener); } private update(patch: Partial): void { this.selections = { ...this.selections, ...patch }; this.emit(); } setSelectedPlan(planId: string): void { this.update({ selectedPlanId: planId }); } setTargetDate(date: Date): void { this.update({ targetDate: date }); } setMissingPeriodActive(periodId: string, isActive: boolean): void { const next = new Set(this.selections.activeMissingPeriodIds); if (isActive) next.add(periodId); else next.delete(periodId); this.update({ activeMissingPeriodIds: next }); } setExtraExcludedDays(count: number): void { this.update({ extraExcludedDays: Math.max(0, Math.floor(count) || 0) }); } setActualMealBlocks(value: number | null): void { this.update({ actualMealBlocks: normalizeBalance(value) }); } setActualFlexDollars(value: number | null): void { this.update({ actualFlexDollars: normalizeBalance(value) }); } private emit(): void { for (const l of this.listeners) l(); } } /** Clamp an entered balance to a non-negative number, or null if blank/invalid. */ function normalizeBalance(value: number | null): number | null { if (value === null || !Number.isFinite(value)) return null; return Math.max(0, value); }