"use client"; import { useState, useMemo, useEffect, useCallback } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine } from "recharts"; import { Sparkles, TrendingUp, TrendingDown, Zap, RotateCcw, ChevronRight, AlertTriangle } from "lucide-react"; import { aiApi } from "@/lib/api"; // ─── Types ──────────────────────────────────────────────────────────────────── interface SimParams { monthlyIncome: number; monthlyExpenses: number; savingsRate: number; investmentReturn: number; loanPayment: number; emergencyMonths: number; } const DEFAULT_PARAMS: SimParams = { monthlyIncome: 10200, monthlyExpenses: 6100, savingsRate: 40, investmentReturn: 7, loanPayment: 450, emergencyMonths: 6, }; const scenarios = [ { id: "aggressive", label: "Aggressive Saver", icon: "🚀", color: "text-emerald-400", bg: "bg-emerald-500/10 border-emerald-500/20", params: { savingsRate: 55, monthlyExpenses: 4600, investmentReturn: 9 }, }, { id: "balanced", label: "Balanced Growth", icon: "⚖️", color: "text-blue-400", bg: "bg-blue-500/10 border-blue-500/20", params: { savingsRate: 40, monthlyExpenses: 6100, investmentReturn: 7 }, }, { id: "conservative", label: "Conservative", icon: "🛡️", color: "text-purple-400", bg: "bg-purple-500/10 border-purple-500/20", params: { savingsRate: 25, monthlyExpenses: 7600, investmentReturn: 4 }, }, ]; // ─── Local projection engine (instant feedback while API loads) ─────────────── function projectBalance(params: SimParams, startBalance: number, months = 36) { const monthlySavings = params.monthlyIncome * (params.savingsRate / 100); const monthlyReturn = params.investmentReturn / 100 / 12; let balance = startBalance; return Array.from({ length: months + 1 }, (_, i) => { if (i > 0) balance = balance * (1 + monthlyReturn) + monthlySavings - params.loanPayment; return { month: i === 0 ? "Now" : `M${i}`, balance: Math.round(balance), savings: Math.round(monthlySavings * i) }; }); } // ─── Custom Tooltip ─────────────────────────────────────────────────────────── interface TooltipProps { active?: boolean; payload?: Array<{ name: string; value: number; stroke?: string; fill?: string }>; label?: string; } function CustomTooltip({ active, payload, label }: TooltipProps) { if (!active || !payload?.length) return null; return (

{label}

{payload.map((entry) => (
{entry.name} ${entry.value?.toLocaleString()}
))}
); } // ─── Slider ─────────────────────────────────────────────────────────────────── function Slider({ label, value, min, max, step, format, onChange, color = "emerald" }: { label: string; value: number; min: number; max: number; step: number; format: (v: number) => string; onChange: (v: number) => void; color?: string; }) { const pct = ((value - min) / (max - min)) * 100; const colorMap: Record = { emerald: "#10b981", blue: "#3b82f6", purple: "#8b5cf6", amber: "#f59e0b", red: "#ef4444", }; const c = colorMap[color] || colorMap.emerald; return (
{label} {format(value)}
onChange(Number(e.target.value))} className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" style={{ zIndex: 10 }} />
); } // ─── Metric Card ────────────────────────────────────────────────────────────── function MetricCard({ label, value, sub, positive, icon }: { label: string; value: string; sub: string; positive: boolean; icon: React.ReactNode; }) { return (

{label}

{icon}

{value}

{sub}

); } // ─── Main Page ──────────────────────────────────────────────────────────────── export default function SimulatorPage() { const [params, setParams] = useState(DEFAULT_PARAMS); const [activeScenario, setActiveScenario] = useState("balanced"); const [startBalance, setStartBalance] = useState(24563); const [apiScenarios, setApiScenarios] = useState | null>(null); const [loadingApi, setLoadingApi] = useState(false); const [apiError, setApiError] = useState(false); // Load real starting balance and API scenarios on mount useEffect(() => { const loadApiData = async () => { setLoadingApi(true); try { const [twinData, scenariosData] = await Promise.all([ aiApi.twinPredict(), aiApi.twinScenarios(6), ]); // Extract starting balance from twin prediction const projections = twinData.monthly_projections; if (projections && projections.length > 0) { setStartBalance(projections[0].balance || 24563); } // Extract scenario end values if (scenariosData.scenarios) { const s = scenariosData.scenarios; const getEnd = (arr: number[]) => arr[arr.length - 1] || 0; setApiScenarios({ conservative: getEnd(s.conservative || []), expected: getEnd(s.expected || []), optimistic: getEnd(s.optimistic || []), }); } setApiError(false); } catch { setApiError(true); } finally { setLoadingApi(false); } }; loadApiData(); }, []); const projection = useMemo(() => projectBalance(params, startBalance), [params, startBalance]); const baseProjection = useMemo(() => projectBalance(DEFAULT_PARAMS, startBalance), [startBalance]); const finalBalance = projection[projection.length - 1].balance; const baseBalance = baseProjection[baseProjection.length - 1].balance; const delta = finalBalance - baseBalance; const monthlySavings = params.monthlyIncome * (params.savingsRate / 100); const emergencyTarget = params.monthlyExpenses * params.emergencyMonths; const monthsToEmergency = Math.ceil(Math.max(0, emergencyTarget - startBalance) / monthlySavings); const update = (key: keyof SimParams) => (v: number) => { setParams((p) => ({ ...p, [key]: v })); setActiveScenario(null); }; const applyScenario = (s: typeof scenarios[0]) => { setParams((p) => ({ ...p, ...s.params })); setActiveScenario(s.id); }; const reset = useCallback(() => { setParams(DEFAULT_PARAMS); setActiveScenario("balanced"); }, []); const comparisonData = projection.map((p, i) => ({ ...p, base: baseProjection[i]?.balance, })); return ( {/* Header */}

What-If Simulator

Model financial scenarios and see your future balance in real-time {loadingApi && · Loading real data...}

{/* API error notice */} {apiError && (

Using local projections — backend offline. Sliders still work with estimated data.

)} {/* API Scenario Cards (from backend) */} {apiScenarios && (
AI 6-Month Forecast (from your real data)
{[ { label: "Conservative", value: apiScenarios.conservative, color: "text-zinc-400" }, { label: "Expected", value: apiScenarios.expected, color: "text-blue-400" }, { label: "Optimistic", value: apiScenarios.optimistic, color: "text-emerald-400" }, ].map((s) => (

{s.label}

${s.value.toLocaleString()}

))}
)} {/* Scenario Presets */}
{scenarios.map((s) => ( applyScenario(s)} className={`flex items-center gap-3 rounded-2xl border p-4 text-left transition-all ${s.bg} ${ activeScenario === s.id ? "ring-1 ring-white/20" : "" }`} > {s.icon}

{s.label}

Apply preset

{activeScenario === s.id &&
} ))}
{/* Controls */}

Adjust Parameters

`$${v.toLocaleString()}`} onChange={update("monthlyIncome")} color="emerald" /> `$${v.toLocaleString()}`} onChange={update("monthlyExpenses")} color="amber" /> `${v}%`} onChange={update("savingsRate")} color="blue" /> `${v}%`} onChange={update("investmentReturn")} color="purple" /> `$${v.toLocaleString()}`} onChange={update("loanPayment")} color="red" /> `${v}mo`} onChange={update("emergencyMonths")} color="emerald" />
{/* Chart + Metrics */}
{/* Metrics */}
= 0 ? "+" : ""}$${Math.abs(delta / 1000).toFixed(1)}k vs baseline`} positive={delta >= 0} icon={} /> = 20} icon={} /> } />
{/* Projection Chart */}

36-Month Projection

Your scenario vs baseline

Your scenario
Baseline
`$${(v / 1000).toFixed(0)}k`} /> } /> {/* Dynamic AI Insight */}

AI Insight

{params.savingsRate >= 50 ? `At ${params.savingsRate}% savings rate, you'll reach $100k in approximately ${Math.ceil((100000 - startBalance) / (params.monthlyIncome * params.savingsRate / 100))} months. That's an aggressive but achievable goal.` : params.savingsRate >= 30 ? `Your ${params.savingsRate}% savings rate is above average. Increasing by just 5% more would add $${Math.round(params.monthlyIncome * 0.05 * 36).toLocaleString()} to your 3-year balance.` : `At ${params.savingsRate}% savings rate, consider reducing discretionary spending. Even a 5% increase would significantly accelerate your goals.`}

); }