'use client' import { useMemo, useState } from 'react' import { CartesianGrid, Line, LineChart, ReferenceDot, ResponsiveContainer, Tooltip, XAxis, YAxis, Bar, BarChart, } from 'recharts' import type { GenericFormulaSim } from '@/lib/interactive-page' import { parseSimExpr } from '@/lib/interactive-page/sim-eval' import { intentColor } from '@/lib/interactive-demo/intent-colors' import { useDarkMode } from '@/components/interactive-demo/demo-speak' import { SimHeading, SimOutputCard, SimSlider } from './sim-controls' const CURVE_SAMPLES = 60 /** Generic slider-driven formula simulator (safe exprs, no eval). */ export function GenericFormulaView({ sim, lang, }: { sim: GenericFormulaSim lang: string }) { const fr = lang.startsWith('fr') const dark = useDarkMode() const [values, setValues] = useState>(() => { const env: Record = {} for (const p of sim.params) env[p.id] = p.defaultValue return env }) const compiled = useMemo( () => sim.computed.map((c) => ({ def: c, parsed: parseSimExpr(c.expr), })), [sim.computed] ) const results = useMemo(() => { const env = { ...values } const out: Record = {} for (const c of compiled) { const v = 'message' in c.parsed ? NaN : c.parsed.evaluate(env) env[c.def.id] = v out[c.def.id] = v } return out }, [compiled, values]) const visual = sim.visual const curve = useMemo(() => { if (visual.kind !== 'curve') return null const xParam = sim.params.find((p) => p.id === visual.xParamId) const parsed = parseSimExpr(visual.expr) if (!xParam || 'message' in parsed) return null // Full env: params + chained computed (visual.expr may reference computed ids) const fullEnv = { ...values, ...results } const pts: { x: number; y: number }[] = [] for (let i = 0; i <= CURVE_SAMPLES; i++) { const x = xParam.min + ((xParam.max - xParam.min) * i) / CURVE_SAMPLES const y = parsed.evaluate({ ...fullEnv, [xParam.id]: x }) pts.push({ x: Number(x.toFixed(4)), y: Number.isFinite(y) ? Number(y.toFixed(6)) : 0 }) } return { pts, xParam, currentX: values[xParam.id], currentY: parsed.evaluate(fullEnv) } }, [visual, sim.params, values, results]) const accent = intentColor('highlight', dark) const gridStroke = dark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)' return (
{sim.intro ? (

{sim.intro}

) : null}
{fr ? 'Paramètres' : 'Parameters'}
{sim.params.map((p) => ( setValues((s) => ({ ...s, [p.id]: v }))} /> ))}
{fr ? 'Résultats' : 'Results'} {sim.visual.kind === 'gauges' ? (
{sim.computed.map((c) => { const v = results[c.id] const maxRef = Math.max( ...sim.computed.map((o) => Math.abs(results[o.id] || 0)), 1 ) const color = intentColor(c.intent, dark) return (
{c.label} {Number.isFinite(v) ? v.toFixed(2) : '—'} {c.unit ? ` ${c.unit}` : ''}
) })}
) : null} {sim.visual.kind === 'bars' ? (
({ name: c.label, value: Number.isFinite(results[c.id]) ? results[c.id] : 0, }))} margin={{ top: 8, right: 8, left: 0, bottom: 4 }} >
) : null} {sim.visual.kind === 'curve' && curve ? (
String(Math.round(v))} /> {Number.isFinite(curve.currentY) ? ( ) : null}
) : null}
{sim.computed.map((c) => ( ))}
{sim.disclaimer ? (

{sim.disclaimer}

) : null}
) }