import {
Activity,
ChevronDown,
ChevronRight,
Minus,
Target,
TrendingDown,
TrendingUp,
} from 'lucide-react';
import { useState } from 'react';
import {
Area,
AreaChart,
Bar,
BarChart,
ReferenceLine,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
const BG = { surface: 'var(--gi-bg-surface)', elevated: 'var(--gi-bg-raised)' };
const BORDER = { subtle: 'rgba(255,255,255,0.04)', muted: 'rgba(255,255,255,0.07)' };
const TEXT = {
primary: 'rgba(255,255,255,0.88)',
secondary: 'rgba(255,255,255,0.55)',
tertiary: 'rgba(255,255,255,0.28)',
muted: 'rgba(255,255,255,0.14)',
};
export interface SimulationScenario {
id: string;
label: string;
color: string;
outputMetric: string;
outputUnit: string;
p5: number;
p25: number;
p50: number;
p75: number;
p95: number;
mean: number;
stdDev: number;
confidence: number;
}
export interface SensitivityDriver {
input: string;
lowValue: number;
highValue: number;
baseOutput: number;
lowOutput: number;
highOutput: number;
unit: string;
}
export interface MonteCarloSimPanelProps {
scenarios: SimulationScenario[];
sensitivityDrivers: SensitivityDriver[];
iterations: number;
compact?: boolean;
}
function formatValue(v: number, unit: string): string {
if (unit === '$' || unit === 'USD') {
if (Math.abs(v) >= 1_000_000) return `$${(v / 1_000_000).toFixed(1)}M`;
if (Math.abs(v) >= 1_000) return `$${(v / 1_000).toFixed(0)}K`;
return `$${v.toFixed(0)}`;
}
if (unit === '%') return `${v.toFixed(1)}%`;
return v.toFixed(1);
}
function DistributionChart({ scenario }: { scenario: SimulationScenario }) {
const bins = 30;
const range = scenario.p95 - scenario.p5;
const binWidth = range / bins;
const data = Array.from({ length: bins }, (_, i) => {
const x = scenario.p5 + i * binWidth;
const z = (x - scenario.mean) / scenario.stdDev;
const density = Math.exp(-0.5 * z * z) / (scenario.stdDev * Math.sqrt(2 * Math.PI));
return {
x: Math.round(x),
density: density * 1000,
label: formatValue(x, scenario.outputUnit),
};
});
return (
Math.abs(d.x - scenario.p50) < Math.abs(data[closest].x - scenario.p50)
? i
: closest,
0,
)}
stroke={scenario.color}
strokeDasharray="3 3"
strokeOpacity={0.6}
/>
[value.toFixed(2), 'Density']}
/>
);
}
function TornadoChart({ drivers, unit }: { drivers: SensitivityDriver[]; unit: string }) {
const sorted = [...drivers].sort(
(a, b) => Math.abs(b.highOutput - b.lowOutput) - Math.abs(a.highOutput - a.lowOutput),
);
const data = sorted.map((d) => ({
name: d.input,
low: d.lowOutput - d.baseOutput,
high: d.highOutput - d.baseOutput,
lowLabel: formatValue(d.lowOutput, unit),
highLabel: formatValue(d.highOutput, unit),
}));
return (
[formatValue(value, unit), 'Impact']}
/>
);
}
export function MonteCarloSimPanel({
scenarios,
sensitivityDrivers,
iterations,
compact,
}: MonteCarloSimPanelProps) {
const [activeScenario, setActiveScenario] = useState(scenarios[0]?.id);
const [showTornado, setShowTornado] = useState(false);
const selected = scenarios.find((s) => s.id === activeScenario) ?? scenarios[0];
if (compact && selected) {
const trend =
selected.p50 > selected.mean ? 'up' : selected.p50 < selected.mean ? 'down' : 'flat';
const TrendIcon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus;
const trendColor = trend === 'up' ? '#6b8f71' : trend === 'down' ? '#c45a4a' : TEXT.tertiary;
return (
Monte Carlo Simulation
{iterations.toLocaleString()} runs
{formatValue(selected.p50, selected.outputUnit)}
Median ({selected.outputMetric})
{formatValue(selected.p5, selected.outputUnit)} —{' '}
{formatValue(selected.p95, selected.outputUnit)}
= 75 ? '#6b8f71' : '#c8953c' }}
>
{selected.confidence}%
Confidence
);
}
return (
Monte Carlo Simulation
{iterations.toLocaleString()} iterations
{scenarios.length > 1 && (
{scenarios.map((s) => (
))}
)}
{selected && (
{[
{
label: 'P5 (Worst)',
value: formatValue(selected.p5, selected.outputUnit),
color: '#c45a4a',
},
{
label: 'P25',
value: formatValue(selected.p25, selected.outputUnit),
color: '#c8953c',
},
{
label: 'P50 (Median)',
value: formatValue(selected.p50, selected.outputUnit),
color: selected.color,
},
{
label: 'P75',
value: formatValue(selected.p75, selected.outputUnit),
color: '#6b8f71',
},
{
label: 'P95 (Best)',
value: formatValue(selected.p95, selected.outputUnit),
color: '#6b8f71',
},
].map((p) => (
))}
Decision Confidence
= 75 ? '#6b8f71' : '#c8953c' }}
/>
= 75 ? '#6b8f71' : '#c8953c' }}
>
{selected.confidence}%
Std Deviation
{formatValue(selected.stdDev, selected.outputUnit)}
{showTornado && sensitivityDrivers.length > 0 && (
Tornado Chart — Key Sensitivity Drivers
)}
)}
);
}