'use client' import { useState, useEffect, useCallback, useRef } from 'react' import { FMScenario, FMResult, FMComputeResponse, InvestorSnapshot } from '../types' export function useFinancialModel(investorId?: string | null) { const [scenarios, setScenarios] = useState([]) const [activeScenarioId, setActiveScenarioId] = useState(null) const [compareMode, setCompareMode] = useState(false) const [results, setResults] = useState>(new Map()) const [loading, setLoading] = useState(true) const [computing, setComputing] = useState(false) const [snapshotStatus, setSnapshotStatus] = useState<'default' | 'saving' | 'saved' | 'restored'>('default') const computeTimer = useRef(null) const snapshotTimer = useRef(null) const snapshotsLoaded = useRef(false) // Load scenarios on mount, then apply snapshots if investor is logged in useEffect(() => { async function load() { try { const res = await fetch('/api/financial-model') if (res.ok) { let data: FMScenario[] = await res.json() // If investor is logged in, restore their snapshots if (investorId && !snapshotsLoaded.current) { try { const snapRes = await fetch('/api/snapshots') if (snapRes.ok) { const { snapshots } = await snapRes.json() as { snapshots: InvestorSnapshot[] } if (snapshots.length > 0) { data = data.map(scenario => { const snapshot = snapshots.find(s => s.scenario_id === scenario.id) if (!snapshot) return scenario return { ...scenario, assumptions: scenario.assumptions.map(a => { const savedValue = snapshot.assumptions[a.key] return savedValue !== undefined ? { ...a, value: savedValue } : a }), } }) setSnapshotStatus('restored') } } } catch { // Snapshot restore failed — use defaults } snapshotsLoaded.current = true } setScenarios(data) const defaultScenario = data.find(s => s.is_default) || data[0] if (defaultScenario) { setActiveScenarioId(defaultScenario.id) } } } catch (err) { console.error('Failed to load financial model:', err) } finally { setLoading(false) } } load() }, [investorId]) // Compute when active scenario changes useEffect(() => { if (activeScenarioId && !results.has(activeScenarioId)) { compute(activeScenarioId) } }, [activeScenarioId]) // eslint-disable-line react-hooks/exhaustive-deps const compute = useCallback(async (scenarioId: string) => { setComputing(true) try { const res = await fetch('/api/financial-model/compute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scenarioId }), }) if (res.ok) { const data: FMComputeResponse = await res.json() setResults(prev => new Map(prev).set(scenarioId, data)) } } catch (err) { console.error('Compute failed:', err) } finally { setComputing(false) } }, []) // Auto-save snapshot (debounced) const saveSnapshot = useCallback(async (scenarioId: string) => { if (!investorId) return const scenario = scenarios.find(s => s.id === scenarioId) if (!scenario) return const assumptions: Record = {} scenario.assumptions.forEach(a => { assumptions[a.key] = a.value }) setSnapshotStatus('saving') try { await fetch('/api/snapshots', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scenario_id: scenarioId, assumptions }), }) setSnapshotStatus('saved') } catch { setSnapshotStatus('default') } }, [investorId, scenarios]) const updateAssumption = useCallback(async (scenarioId: string, key: string, value: number | number[]) => { // Optimistic update in local state setScenarios(prev => prev.map(s => { if (s.id !== scenarioId) return s return { ...s, assumptions: s.assumptions.map(a => a.key === key ? { ...a, value } : a), } })) // Save to DB await fetch('/api/financial-model/assumptions', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scenarioId, key, value }), }) // Debounced recompute if (computeTimer.current) clearTimeout(computeTimer.current) computeTimer.current = setTimeout(() => compute(scenarioId), 300) // Debounced snapshot save (2s after last change) if (snapshotTimer.current) clearTimeout(snapshotTimer.current) snapshotTimer.current = setTimeout(() => saveSnapshot(scenarioId), 2000) }, [compute, saveSnapshot]) const resetToDefaults = useCallback(async (scenarioId: string) => { // Reload from server (without snapshots) try { const res = await fetch('/api/financial-model') if (res.ok) { const data: FMScenario[] = await res.json() const defaultScenario = data.find(s => s.id === scenarioId) if (defaultScenario) { setScenarios(prev => prev.map(s => s.id === scenarioId ? defaultScenario : s)) // Delete snapshot if (investorId) { await fetch(`/api/snapshots?id=${scenarioId}`, { method: 'DELETE' }) } setSnapshotStatus('default') compute(scenarioId) } } } catch { // ignore } }, [compute, investorId]) const computeAll = useCallback(async () => { for (const s of scenarios) { await compute(s.id) } }, [scenarios, compute]) const activeScenario = scenarios.find(s => s.id === activeScenarioId) || null const activeResults = activeScenarioId ? results.get(activeScenarioId) || null : null return { scenarios, activeScenario, activeScenarioId, setActiveScenarioId, activeResults, results, loading, computing, compareMode, setCompareMode, compute, computeAll, updateAssumption, resetToDefaults, snapshotStatus, } }