/** Zustand store for Potential Analysis page state. */ import { create } from 'zustand'; import type { PinchResult, HPIResult, StatusQuoResult, EnergyDemandLocal, } from '../types/analysis'; interface AnalysisStore { // Stream selection: "stream_{procIdx}_{streamIdx}" → boolean selectedStreams: Record; // Energy demands (user-entered) energyDemands: EnergyDemandLocal[]; // Pinch controls tMin: number; showShifted: boolean; // Results from backend pinchResult: PinchResult | null; hpiResult: HPIResult | null; statusQuoResult: StatusQuoResult | null; // Heat pump selection for chart selectedHPTypes: string[]; // Loading states pinchLoading: boolean; hpiLoading: boolean; // Scenarios: Array of saved states scenarios: Array<{ id: string; name: string; selectedStreams: Record; energyDemands: EnergyDemandLocal[]; tMin: number; // Results for comparison pinchResult?: PinchResult | null; hpiResult?: HPIResult | null; statusQuoResult?: StatusQuoResult | null; }>; activeScenarioId: string | null; // Actions setSelectedStream: (key: string, val: boolean) => void; setSelectedStreams: (streams: Record) => void; setStreamsForProcess: (pIdx: number, val: boolean, processes: any[]) => void; toggleStreamSelection: (pIdx: number, sIdx: number) => void; initSelectedStreams: (keys: string[]) => void; setEnergyDemands: (demands: EnergyDemandLocal[]) => void; addEnergyDemand: (demand: EnergyDemandLocal) => void; removeEnergyDemand: (index: number) => void; updateEnergyDemand: (index: number, demand: Partial) => void; setTMin: (val: number) => void; setShowShifted: (val: boolean) => void; setPinchResult: (result: PinchResult | null) => void; setHPIResult: (result: HPIResult | null) => void; setStatusQuoResult: (result: StatusQuoResult | null) => void; setSelectedHPTypes: (types: string[]) => void; setPinchLoading: (val: boolean) => void; setHPILoading: (val: boolean) => void; saveScenario: (name?: string) => void; loadScenario: (index: number) => void; deleteScenario: (index: number) => void; renameScenario: (index: number, newName: string) => void; clearActiveScenario: () => void; } // Helper to update the scenario list if an active scenario is set const syncScenario = (state: AnalysisStore) => { if (!state.activeScenarioId) return {}; return { scenarios: state.scenarios.map(sc => sc.id === state.activeScenarioId ? { ...sc, selectedStreams: { ...state.selectedStreams }, energyDemands: JSON.parse(JSON.stringify(state.energyDemands)), tMin: state.tMin, pinchResult: state.pinchResult ? JSON.parse(JSON.stringify(state.pinchResult)) : null, hpiResult: state.hpiResult ? JSON.parse(JSON.stringify(state.hpiResult)) : null, statusQuoResult: state.statusQuoResult ? JSON.parse(JSON.stringify(state.statusQuoResult)) : null, } : sc ) }; }; export const useAnalysisStore = create((set) => ({ selectedStreams: {}, energyDemands: [ { heat_demand: 0, cooling_demand: 0, selected_heat_streams: [], selected_cooling_streams: [], }, ], tMin: 10, showShifted: false, pinchResult: null, hpiResult: null, statusQuoResult: null, selectedHPTypes: [], pinchLoading: false, hpiLoading: false, scenarios: [], activeScenarioId: null, setSelectedStream: (key, val) => { set((s) => ({ selectedStreams: { ...s.selectedStreams, [key]: val } })); set((s) => syncScenario(s)); }, setSelectedStreams: (streams) => { set({ selectedStreams: streams }); set((s) => syncScenario(s)); }, setStreamsForProcess: (pIdx, val, processes) => { set((s) => { const next = { ...s.selectedStreams }; const proc = processes[pIdx]; if (proc) { (proc.streams || []).forEach((_: any, si: number) => { next[`stream_${pIdx}_${si}`] = val; }); } return { selectedStreams: next }; }); set((s) => syncScenario(s)); }, toggleStreamSelection: (pIdx, sIdx) => { set((s) => { const key = `stream_${pIdx}_${sIdx}`; const current = s.selectedStreams[key] !== false; return { selectedStreams: { ...s.selectedStreams, [key]: !current } }; }); set((s) => syncScenario(s)); }, initSelectedStreams: (keys) => set(() => { const streams: Record = {}; keys.forEach((k) => (streams[k] = true)); return { selectedStreams: streams }; }), setEnergyDemands: (demands) => { set({ energyDemands: demands }); set((s) => syncScenario(s)); }, addEnergyDemand: (demand) => { set((s) => ({ energyDemands: [...s.energyDemands, demand] })); set((s) => syncScenario(s)); }, removeEnergyDemand: (index) => { set((s) => ({ energyDemands: s.energyDemands.filter((_, i) => i !== index), })); set((s) => syncScenario(s)); }, updateEnergyDemand: (index, demand) => { set((s) => ({ energyDemands: s.energyDemands.map((d, i) => i === index ? { ...d, ...demand } : d ), })); set((s) => syncScenario(s)); }, setTMin: (val) => { set({ tMin: val }); set((s) => syncScenario(s)); }, setShowShifted: (val) => set({ showShifted: val }), setPinchResult: (result) => { set((s) => { const newState: any = { pinchResult: result }; // Auto-save "All streams" if it's the first time we get a result and no scenarios exist if (result && s.scenarios.length === 0 && !s.activeScenarioId) { const newScen = { id: crypto.randomUUID(), name: 'All streams', selectedStreams: { ...s.selectedStreams }, energyDemands: JSON.parse(JSON.stringify(s.energyDemands)), tMin: s.tMin, pinchResult: JSON.parse(JSON.stringify(result)), hpiResult: null, statusQuoResult: null, }; newState.scenarios = [newScen]; newState.activeScenarioId = newScen.id; } return newState; }); set((s) => syncScenario(s)); }, setHPIResult: (result) => { set({ hpiResult: result }); set((s) => syncScenario(s)); }, setStatusQuoResult: (result) => { set({ statusQuoResult: result }); set((s) => syncScenario(s)); }, setSelectedHPTypes: (types) => set({ selectedHPTypes: types }), setPinchLoading: (val) => set({ pinchLoading: val }), setHPILoading: (val) => set({ hpiLoading: val }), saveScenario: (name) => set((s) => { const nextIndex = s.scenarios.length + 1; const finalName = name?.trim() || `Scenario ${nextIndex}`; const newScen = { id: crypto.randomUUID(), name: finalName, selectedStreams: { ...s.selectedStreams }, energyDemands: JSON.parse(JSON.stringify(s.energyDemands)), tMin: s.tMin, pinchResult: s.pinchResult ? JSON.parse(JSON.stringify(s.pinchResult)) : null, hpiResult: s.hpiResult ? JSON.parse(JSON.stringify(s.hpiResult)) : null, statusQuoResult: s.statusQuoResult ? JSON.parse(JSON.stringify(s.statusQuoResult)) : null, }; return { scenarios: [...s.scenarios, newScen], activeScenarioId: newScen.id }; }), loadScenario: (index) => set((s) => { const sc = s.scenarios[index]; if (!sc) return {}; return { activeScenarioId: sc.id, selectedStreams: { ...sc.selectedStreams }, energyDemands: JSON.parse(JSON.stringify(sc.energyDemands)), tMin: sc.tMin, pinchResult: sc.pinchResult ? JSON.parse(JSON.stringify(sc.pinchResult)) : null, hpiResult: sc.hpiResult ? JSON.parse(JSON.stringify(sc.hpiResult)) : null, statusQuoResult: sc.statusQuoResult ? JSON.parse(JSON.stringify(sc.statusQuoResult)) : null, }; }), deleteScenario: (index) => set((s) => { const sc = s.scenarios[index]; const isActive = sc && sc.id === s.activeScenarioId; return { scenarios: s.scenarios.filter((_, i) => i !== index), activeScenarioId: isActive ? null : s.activeScenarioId }; }), renameScenario: (index, newName) => set((s) => ({ scenarios: s.scenarios.map((sc, i) => i === index ? { ...sc, name: newName } : sc) })), clearActiveScenario: () => set({ activeScenarioId: null }) }));