import type { AppState, StateRepository } from '@domain/ports/StateRepository'; import { DEFAULT_TARGETS } from '@domain/value-objects/GaugeType'; interface ElectronAPI { saveData: (json: string) => void; loadData: () => Promise; } function getElectronAPI(): ElectronAPI | null { if (typeof window !== 'undefined' && (window as any).electronAPI) { return (window as any).electronAPI; } return null; } export class LocalStorageRepository implements StateRepository { private readonly storageKey = 'dd3v3'; async load(): Promise { try { let raw: string | null = null; const api = getElectronAPI(); if (api) raw = await api.loadData(); if (!raw && typeof localStorage !== 'undefined') { raw = localStorage.getItem(this.storageKey) || localStorage.getItem('dd3v2'); } if (!raw) return null; return this.deserialize(raw); } catch { return null; } } save(state: AppState): void { try { const d = JSON.parse(JSON.stringify(state)); // Reset runtime state before persisting d.enclos.forEach((e: any) => { e.timer.running = false; e.alerted = {}; }); const json = JSON.stringify(d); const api = getElectronAPI(); if (api) api.saveData(json); if (typeof localStorage !== 'undefined') { localStorage.setItem(this.storageKey, json); } } catch (e) { console.error('Erreur sauvegarde:', (e as Error).message); } } private deserialize(raw: string): AppState { const d = JSON.parse(raw); const state: AppState = { enclos: d.enclos || [], activeId: d.activeId ?? null, nextEnclosId: d.nextEnclosId || 1, alarmSound: d.alarmSound || 'arpege', notifsEnabled: d.notifsEnabled !== undefined ? d.notifsEnabled : true, ntfyTopic: d.ntfyTopic || '', archivedStats: d.archivedStats || [], inventaire: d.inventaire || {}, workflows: d.workflows || [], accouplements: d.accouplements || [], }; // Migration: old ntfyUrl format → ntfyTopic if (!state.ntfyTopic && d.ntfyUrl) { const m = d.ntfyUrl.match(/\/([^\/]+)$/); if (m) state.ntfyTopic = m[1]; } // Migrate enclos data state.enclos.forEach((enc: any) => { enc.timer = enc.timer || { running: false, startTime: null, pausedAt: null, pausedMs: 0, snapGauges: {}, snapStats: {} }; enc.timer.running = false; enc.alerted = {}; if (enc.gaugeLevels.mangeoire === undefined) enc.gaugeLevels.mangeoire = 0; enc.dragodindes.forEach((dd: any) => { if (dd.stats.xp === undefined) dd.stats.xp = 1; // Migration: old serenite target → gauge-based targets if (dd.targets.serenite !== undefined && dd.targets.baffeur === undefined) { const old = { ...dd.targets }; dd.targets = { baffeur: old.serenite ?? -5000, caresseur: Math.max(0, old.serenite ?? 40), foudroyeur: old.endurance ?? 20000, abreuvoir: old.maturite ?? 20000, dragofesse: old.amour ?? 20000, mangeoire: 100, }; } Object.keys(DEFAULT_TARGETS).forEach(k => { if (dd.targets[k] === undefined) dd.targets[k] = (DEFAULT_TARGETS as any)[k]; }); }); }); if (!state.activeId && state.enclos.length) { state.activeId = state.enclos[0].id; } return state; } }