/**
 * What-If — interactive Monte Carlo. Compact form on the left, fan
 * chart on the right. Hits POST /simulate (no DB write); ~1-3s for 5k
 * paths.
 *
 * Layout: anchor numbers (NW / spend / horizon) up top; plan (where /
 * how / when) and returns model in compact cards; advanced knobs
 * folded away. Hints live in ⓘ popovers, not always-visible
 * paragraphs, so the form fits on a typical desktop viewport.
 *
 * Allocation is hardcoded to 100% stocks at the API layer — the user
 * is single-allocation, so the glide-path knob was noise. See
 * `api/simulate.py::_project`.
 */
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';

import { api, type SimulateRequest, type SimulateResult } from '@/api/client';
import { FanChart } from '@/components/FanChart';
import { InfoTip } from '@/components/InfoTip';
import { SegmentedControl, type SegmentedOption } from '@/components/SegmentedControl';
import { gbp, pct } from '@/lib/format';

const JURISDICTIONS = ['uk', 'cyprus', 'bulgaria', 'malaysia', 'thailand', 'uae', 'nomad'];

type Strategy = 'trinity' | 'guyton_klinger' | 'vpw' | 'vpw_floor' | 'custom';
type ReturnsMode = 'shiller' | 'manual' | 'wealthfolio';

const STRATEGY_OPTIONS: ReadonlyArray<SegmentedOption<Strategy>> = [
  { value: 'trinity', label: 'Trinity' },
  { value: 'guyton_klinger', label: 'Guyton-Klinger' },
  { value: 'vpw', label: 'VPW' },
  { value: 'vpw_floor', label: 'VPW + floor' },
  { value: 'custom', label: 'Custom' },
];

const RETURNS_OPTIONS: ReadonlyArray<SegmentedOption<ReturnsMode>> = [
  { value: 'shiller', label: 'Historical' },
  { value: 'manual', label: 'Manual %' },
  { value: 'wealthfolio', label: 'Wealthfolio' },
];

const STRATEGY_NOTES: Record<Strategy, string> = {
  trinity:
    'Withdraw your "Annual spending" amount in year 1, then keep that real-£ amount fixed. Simple Trinity-style — never adapts to market crashes.',
  guyton_klinger:
    'Spending in year 1, then guardrails: cut 10% if implied withdrawal rate exceeds 120% of starting (and >15y left), raise 10% if it drops below 80%. Adapts to markets.',
  vpw: 'Variable Percentage Withdrawal — each year, withdraw a percentage based on years left and expected real return. Ignores "Annual spending"; algorithmic. Mathematically can\'t fail, but income swings can be wide.',
  vpw_floor:
    'VPW with a hard real-£ floor: never withdraw less than the floor, even if VPW says you should. Trades guaranteed lifestyle against ruin risk in bad sequences.',
  custom:
    'Pick everything: initial spending (above), an annual real-£ adjustment (e.g. -0.5%/yr to spend less with age), and an optional drawdown guardrail.',
};

const RETURNS_NOTES: Record<ReturnsMode, string> = {
  shiller:
    'Block-bootstrap of US historical real returns (Shiller 1871+). Broadest regime coverage — includes 1929/1973/2000/2008-style bad sequences. Best default for stress-testing.',
  manual:
    'Every year, every path returns the % you type. Deterministic — no fan, no volatility. Useful for sanity checks ("what if my real return is exactly 5%?").',
  wealthfolio:
    'Block-bootstrap of your actual blended portfolio returns from wealthfolio_sync (~6 years, 2020-present). Reflects your real account mix but biased to the recent regime.',
};

const JURISDICTION_NOTES: Record<string, string> = {
  uk: 'UK 2026/27 PAYE + NI + CGT + dividend rules. Personal allowance tapers above £100k; pension withdrawals 25% tax-free.',
  cyprus:
    'Cyprus 60-day non-dom: 17-year exemption on foreign dividends + interest. 2.65% GeSY healthcare levy capped at €180k.',
  bulgaria: 'Flat 10% on worldwide income. EU/EEA capital gains exempt (we apply 10% conservatively).',
  malaysia: 'Foreign-sourced income exempt through 2036. Effective 0% on a typical retiree withdrawal.',
  thailand: 'Foreign-sourced income exempt (v1; the 2024 remittance rule is deferred in this model).',
  uae: 'No personal income tax, no levy. Effective 0%.',
  nomad: 'Tax-free baseline + a 1% regulatory-risk premium to hedge against OECD/CRS rules tightening.',
};

const DEFAULTS: SimulateRequest = {
  jurisdiction: 'cyprus',
  strategy: 'guyton_klinger',
  leave_uk_year: 2,
  spending_gbp: '60000',
  nw_seed_gbp: '1500000',
  savings_per_year_gbp: '0',
  horizon_years: 60,
  floor_gbp: null,
  n_paths: 5000,
  seed: 42,
  returns_mode: 'shiller',
  manual_real_return_pct: '0.046',
  annual_real_adjust_pct: '0',
  guardrail_threshold_pct: null,
  guardrail_cut_pct: '0.10',
};

export function WhatIf() {
  const [form, setForm] = useState<SimulateRequest>(DEFAULTS);
  const [nwAutoFilled, setNwAutoFilled] = useState(false);
  const navigate = useNavigate();
  const qc = useQueryClient();

  // Pre-fill NW seed from the latest Wealthfolio snapshot the first
  // time it loads, so opening /what-if always starts from real numbers.
  // The user can still edit; we won't clobber their input on later refetches.
  const nw = useQuery({ queryKey: ['networth', 'current'], queryFn: api.networth.current });
  useEffect(() => {
    if (nwAutoFilled || !nw.data || nw.data.accounts.length === 0) return;
    const rounded = String(Math.round(Number(nw.data.total_gbp)));
    setForm((f) => ({ ...f, nw_seed_gbp: rounded }));
    setNwAutoFilled(true);
  }, [nw.data, nwAutoFilled]);

  const sim = useMutation({
    mutationFn: (req: SimulateRequest) => api.simulate(req),
  });

  const save = useMutation({
    mutationFn: (name: string) =>
      api.scenarios.create({
        name,
        jurisdiction: form.jurisdiction,
        strategy: form.strategy,
        leave_uk_year: form.leave_uk_year,
        glide_path: 'static_60_40',
        spending_gbp: form.spending_gbp,
        nw_seed_gbp: form.nw_seed_gbp,
        savings_per_year_gbp: form.savings_per_year_gbp,
        horizon_years: form.horizon_years,
      }),
    onSuccess: (s) => {
      qc.invalidateQueries({ queryKey: ['scenarios'] });
      navigate(`/scenarios/${s.id}`);
    },
  });

  const onSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const isCustom = form.strategy === 'custom';
    sim.mutate({
      ...form,
      floor_gbp: form.strategy === 'vpw_floor' ? form.floor_gbp : null,
      manual_real_return_pct:
        form.returns_mode === 'manual' ? form.manual_real_return_pct : null,
      annual_real_adjust_pct: isCustom ? form.annual_real_adjust_pct : '0',
      guardrail_threshold_pct: isCustom ? form.guardrail_threshold_pct : null,
      guardrail_cut_pct: isCustom ? form.guardrail_cut_pct : '0.10',
    });
  };

  const onSaveAs = () => {
    const suggested = `${form.jurisdiction}-${form.strategy}-leave-y${form.leave_uk_year}`;
    const name = prompt('Save as scenario — name:', suggested);
    if (!name?.trim()) return;
    save.mutate(name.trim());
  };

  const update = <K extends keyof SimulateRequest>(key: K, value: SimulateRequest[K]) =>
    setForm((f) => ({ ...f, [key]: value }));

  const strategy = form.strategy as Strategy;
  const returnsMode = (form.returns_mode ?? 'shiller') as ReturnsMode;

  return (
    <section className="space-y-6">
      <header>
        <h1 className="text-3xl font-semibold tracking-tight">What if…</h1>
        <p className="text-sm text-slate-500">
          Run a single Monte Carlo against the engine. No data persisted. 100% stocks.
        </p>
      </header>

      <div className="grid grid-cols-1 lg:grid-cols-[480px_1fr] gap-6 items-start">
        <form onSubmit={onSubmit} className="space-y-4 sticky top-4">
          <AnchorNumbers form={form} update={update} />

          <PlanCard form={form} update={update} strategy={strategy} />

          <ReturnsCard form={form} update={update} returnsMode={returnsMode} />

          <AdvancedCard form={form} update={update} />

          <button
            type="submit"
            disabled={sim.isPending}
            className="w-full rounded-lg bg-slate-900 text-white text-sm font-semibold px-4 py-3 hover:bg-slate-800 disabled:opacity-60 disabled:cursor-not-allowed shadow-sm"
          >
            {sim.isPending ? 'Running…' : 'Run simulation'}
          </button>
        </form>

        <div className="space-y-4 min-w-0">
          {sim.isError && (
            <div className="rounded-md border border-red-200 bg-red-50 p-4 text-red-800 text-sm">
              {String((sim.error as Error)?.message ?? sim.error)}
            </div>
          )}
          {!sim.data && !sim.isPending && !sim.isError && (
            <div className="rounded-lg border border-slate-200 bg-white p-12 text-center text-slate-500">
              Set parameters on the left and run a simulation.
            </div>
          )}
          {sim.isPending && (
            <div className="rounded-lg border border-slate-200 bg-white p-12 text-center text-slate-500">
              Running Monte Carlo…
            </div>
          )}
          {sim.data && (
            <>
              <Results result={sim.data} horizon={form.horizon_years ?? 60} />
              <div className="flex items-center gap-3">
                <button
                  type="button"
                  onClick={onSaveAs}
                  disabled={save.isPending}
                  className="rounded-md border border-slate-300 bg-white text-sm px-4 py-2 hover:bg-slate-50 disabled:opacity-60"
                >
                  {save.isPending ? 'Saving…' : 'Save as scenario'}
                </button>
                {save.isError && (
                  <span className="text-xs text-red-700">
                    {String((save.error as Error)?.message ?? save.error)}
                  </span>
                )}
              </div>
            </>
          )}

          <AboutTheModel />
        </div>
      </div>
    </section>
  );
}

// ── Sections ─────────────────────────────────────────────────────────

function AnchorNumbers({
  form,
  update,
}: {
  form: SimulateRequest;
  update: <K extends keyof SimulateRequest>(k: K, v: SimulateRequest[K]) => void;
}) {
  return (
    <div className="rounded-lg border border-slate-200 bg-white p-5 shadow-sm">
      <div className="grid grid-cols-[1.4fr_1.2fr_0.7fr] gap-3">
        <BigNumber
          label="NW seed"
          prefix="£"
          value={form.nw_seed_gbp}
          onChange={(v) => update('nw_seed_gbp', v)}
        />
        <BigNumber
          label="Annual spending"
          prefix="£"
          value={form.spending_gbp}
          onChange={(v) => update('spending_gbp', v)}
        />
        <BigNumber
          label="Horizon"
          suffix="yrs"
          value={String(form.horizon_years ?? 60)}
          onChange={(v) => update('horizon_years', clampInt(v, 5, 100))}
          step={1}
        />
      </div>
    </div>
  );
}

function PlanCard({
  form,
  update,
  strategy,
}: {
  form: SimulateRequest;
  update: <K extends keyof SimulateRequest>(k: K, v: SimulateRequest[K]) => void;
  strategy: Strategy;
}) {
  return (
    <div className="rounded-lg border border-slate-200 bg-white p-4 shadow-sm space-y-4">
      <div className="grid grid-cols-2 gap-3">
        <div>
          <SmallLabel
            text="Jurisdiction"
            tip={JURISDICTION_NOTES[form.jurisdiction]}
          />
          <select
            value={form.jurisdiction}
            onChange={(e) => update('jurisdiction', e.target.value)}
            className="mt-1 w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-slate-400"
          >
            {JURISDICTIONS.map((j) => (
              <option key={j} value={j}>
                {j}
              </option>
            ))}
          </select>
        </div>
        <div>
          <SmallLabel text="Years until leaving UK" />
          <input
            type="number"
            value={form.leave_uk_year}
            min={0}
            max={60}
            onChange={(e) => update('leave_uk_year', clampInt(e.target.value, 0, 60))}
            className="mt-1 w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
          />
        </div>
      </div>

      <div>
        <SmallLabel text="Strategy" tip={STRATEGY_NOTES[strategy]} />
        <div className="mt-1">
          <SegmentedControl<Strategy>
            value={strategy}
            onChange={(v) => update('strategy', v)}
            options={STRATEGY_OPTIONS}
            size="sm"
          />
        </div>
      </div>

      {strategy === 'vpw_floor' && (
        <div>
          <SmallLabel
            text="Floor (£/yr)"
            tip="Hard minimum withdrawal — VPW never dips below this even when the schedule says it should."
          />
          <input
            type="number"
            value={Number(form.floor_gbp ?? 40000)}
            min={0}
            onChange={(e) => update('floor_gbp', e.target.value)}
            className="mt-1 w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
          />
        </div>
      )}

      {strategy === 'custom' && (
        <div className="rounded-md bg-slate-50 border border-slate-200 p-3 grid grid-cols-3 gap-3">
          <div>
            <SmallLabel
              text="Real adjust %/yr"
              tip="0 = constant real £ (Trinity shape). Positive grows spending each year (e.g. 0.02 = +2%/yr for healthcare). Negative shrinks (e.g. -0.005 to slow down with age)."
            />
            <input
              type="number"
              value={form.annual_real_adjust_pct ?? '0'}
              step="0.001"
              min={-0.1}
              max={0.1}
              onChange={(e) => update('annual_real_adjust_pct', e.target.value)}
              className="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-xs tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
            />
          </div>
          <div>
            <SmallLabel
              text="Guardrail trigger"
              tip="Fraction of starting NW that triggers a spending cut. e.g. 0.80 = cut once portfolio falls below 80% of seed. Leave blank to disable."
            />
            <input
              type="number"
              value={form.guardrail_threshold_pct ?? ''}
              step="0.05"
              min={0}
              max={1}
              placeholder="(off)"
              onChange={(e) =>
                update('guardrail_threshold_pct', e.target.value === '' ? null : e.target.value)
              }
              className="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-xs tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
            />
          </div>
          <div>
            <SmallLabel
              text="Guardrail cut"
              tip="Fraction by which to cut last year's withdrawal each triggered year. e.g. 0.10 = -10%."
            />
            <input
              type="number"
              value={form.guardrail_cut_pct ?? '0.10'}
              step="0.05"
              min={0}
              max={1}
              onChange={(e) => update('guardrail_cut_pct', e.target.value)}
              className="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-xs tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
            />
          </div>
        </div>
      )}
    </div>
  );
}

function ReturnsCard({
  form,
  update,
  returnsMode,
}: {
  form: SimulateRequest;
  update: <K extends keyof SimulateRequest>(k: K, v: SimulateRequest[K]) => void;
  returnsMode: ReturnsMode;
}) {
  return (
    <div className="rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
      <SmallLabel text="Returns model" tip={RETURNS_NOTES[returnsMode]} />
      <div className="mt-1 flex flex-wrap items-center gap-3">
        <SegmentedControl<ReturnsMode>
          value={returnsMode}
          onChange={(v) => update('returns_mode', v)}
          options={RETURNS_OPTIONS}
          size="sm"
        />
        {returnsMode === 'manual' && (
          <label className="flex items-center gap-2 text-xs text-slate-600">
            <input
              type="number"
              value={form.manual_real_return_pct ?? '0.046'}
              step="0.001"
              min={-0.5}
              max={1}
              onChange={(e) => update('manual_real_return_pct', e.target.value)}
              className="w-24 rounded-md border border-slate-300 bg-white px-2 py-1.5 text-xs tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
            />
            <span>real / yr (e.g. 0.046 = 4.6%)</span>
          </label>
        )}
      </div>
    </div>
  );
}

function AdvancedCard({
  form,
  update,
}: {
  form: SimulateRequest;
  update: <K extends keyof SimulateRequest>(k: K, v: SimulateRequest[K]) => void;
}) {
  return (
    <details className="rounded-lg border border-slate-200 bg-white shadow-sm group">
      <summary className="cursor-pointer select-none px-4 py-3 text-sm font-medium text-slate-700 flex items-center justify-between hover:bg-slate-50 rounded-lg">
        <span>Advanced</span>
        <span className="text-xs text-slate-400 group-open:rotate-180 transition-transform">▾</span>
      </summary>
      <div className="px-4 pb-4 pt-1 grid grid-cols-3 gap-3">
        <div>
          <SmallLabel
            text="Annual savings"
            tip="Real-£ added each year (e.g. while still working). 0 = pure decumulation."
          />
          <input
            type="number"
            value={Number(form.savings_per_year_gbp ?? 0)}
            min={0}
            onChange={(e) => update('savings_per_year_gbp', e.target.value)}
            className="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-sm tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
          />
        </div>
        <div>
          <SmallLabel
            text="MC paths"
            tip="More paths = tighter percentile estimates but slower. 5k is a sweet spot; 50k for final."
          />
          <input
            type="number"
            value={form.n_paths ?? 5000}
            min={100}
            max={50000}
            step={100}
            onChange={(e) => update('n_paths', clampInt(e.target.value, 100, 50000))}
            className="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-sm tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
          />
        </div>
        <div>
          <SmallLabel text="Seed" tip="Same seed = same fan. Change to verify a result is robust." />
          <input
            type="number"
            value={form.seed ?? 42}
            onChange={(e) => update('seed', clampInt(e.target.value, 0, 999999))}
            className="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-sm tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400"
          />
        </div>
      </div>
    </details>
  );
}

// ── Primitives ───────────────────────────────────────────────────────

function SmallLabel({ text, tip }: { text: string; tip?: string }) {
  return (
    <div className="flex items-center gap-1.5">
      <span className="text-[11px] uppercase tracking-wide text-slate-500 font-medium">{text}</span>
      {tip && <InfoTip text={tip} label={text} />}
    </div>
  );
}

function BigNumber({
  label,
  value,
  onChange,
  prefix,
  suffix,
  step = 1,
}: {
  label: string;
  value: string;
  onChange: (v: string) => void;
  prefix?: string;
  suffix?: string;
  step?: number;
}) {
  return (
    <label className="block min-w-0">
      <span className="text-[11px] uppercase tracking-wide text-slate-500 font-medium">
        {label}
      </span>
      <div className="mt-1 relative">
        {prefix && (
          <span className="absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-400 text-base pointer-events-none">
            {prefix}
          </span>
        )}
        <input
          type="number"
          value={value}
          step={step}
          min={0}
          onChange={(e) => onChange(e.target.value)}
          className={`w-full rounded-md border border-slate-300 bg-white py-2 text-xl font-semibold tabular-nums focus:outline-none focus:ring-2 focus:ring-slate-400 ${
            prefix ? 'pl-6' : 'pl-2.5'
          } ${suffix ? 'pr-9' : 'pr-2.5'}`}
        />
        {suffix && (
          <span className="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-400 text-xs pointer-events-none">
            {suffix}
          </span>
        )}
      </div>
    </label>
  );
}

function clampInt(raw: string, min: number, max: number): number {
  const n = Math.round(Number(raw));
  if (!Number.isFinite(n)) return min;
  return Math.min(max, Math.max(min, n));
}

// ── Result panel + about ─────────────────────────────────────────────

function AboutTheModel() {
  return (
    <details className="rounded-lg border border-slate-200 bg-white p-5 group">
      <summary className="cursor-pointer text-base font-semibold flex items-center justify-between">
        <span>About the model</span>
        <span className="text-xs text-slate-500 group-open:hidden">Click to expand</span>
      </summary>
      <div className="mt-4 space-y-5 text-sm text-slate-700">
        <Section title="Withdrawal strategies">
          <Term name="Trinity 4%">{STRATEGY_NOTES.trinity}</Term>
          <Term name="Guyton-Klinger guardrails">{STRATEGY_NOTES.guyton_klinger}</Term>
          <Term name="VPW">{STRATEGY_NOTES.vpw}</Term>
          <Term name="VPW + floor">{STRATEGY_NOTES.vpw_floor}</Term>
          <Term name="Custom spending plan">{STRATEGY_NOTES.custom}</Term>
        </Section>
        <Section title="Tax jurisdictions">
          <Term name="UK">{JURISDICTION_NOTES.uk}</Term>
          <Term name="Cyprus">{JURISDICTION_NOTES.cyprus}</Term>
          <Term name="Bulgaria">{JURISDICTION_NOTES.bulgaria}</Term>
          <Term name="Malaysia">{JURISDICTION_NOTES.malaysia}</Term>
          <Term name="Thailand">{JURISDICTION_NOTES.thailand}</Term>
          <Term name="UAE">{JURISDICTION_NOTES.uae}</Term>
          <Term name="Nomad (no fixed residency)">{JURISDICTION_NOTES.nomad}</Term>
        </Section>
        <Section title="How the engine treats tax">
          <p>
            Each year the strategy proposes a real-£ withdrawal <code>w</code>; the chosen
            jurisdiction's tax engine computes <code>tax(w)</code>; the portfolio drops by
            <code> w + tax(w)</code>. Higher-tax jurisdictions therefore drain the portfolio
            faster and lower the success rate — switching jurisdiction visibly moves the fan,
            not just the lifetime-tax cell.
          </p>
        </Section>
        <Section title="Returns model">
          <Term name="Historical (Shiller 1871+)">{RETURNS_NOTES.shiller}</Term>
          <Term name="Manual real return">{RETURNS_NOTES.manual}</Term>
          <Term name="My Wealthfolio history">{RETURNS_NOTES.wealthfolio}</Term>
          <p className="pt-1">
            Each path resamples blocks independently so sequence-of-returns risk is preserved.
            Long-run benchmarks for context: 60/40 real ≈ 4.6%; equities ~9.5% nominal /
            17% volatility; bonds ~5%/8%.
          </p>
        </Section>
        <Section title="Allocation">
          <p>
            All What-If runs use 100% stocks. The glide-path knob was removed in May 2026
            — the user is single-allocation in real life, so simulating 60/40 mixes was noise.
            Persisted Cartesian scenarios still carry their own glide string.
          </p>
        </Section>
        <Section title="Success rate">
          <p>
            A path counts as a success if the portfolio stays positive through every interim
            year. The very last year-end is excluded because VPW deliberately drains to ~0
            at the horizon by construction.
          </p>
        </Section>
      </div>
    </details>
  );
}

function Section({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <div>
      <h3 className="text-sm font-semibold text-slate-900 mb-2">{title}</h3>
      <div className="space-y-2">{children}</div>
    </div>
  );
}

function Term({ name, children }: { name: string; children: React.ReactNode }) {
  return (
    <div>
      <span className="font-medium">{name}.</span>{' '}
      <span className="text-slate-600">{children}</span>
    </div>
  );
}

function Results({ result, horizon }: { result: SimulateResult; horizon: number }) {
  return (
    <>
      <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
        <Stat label="Success rate" value={pct(result.success_rate)} accent />
        <Stat label="Median ending NW" value={gbp(result.p50_ending_gbp)} />
        <Stat label="P10 ending" value={gbp(result.p10_ending_gbp)} />
        <Stat label="P90 ending" value={gbp(result.p90_ending_gbp)} />
        <Stat label="Median lifetime tax" value={gbp(result.median_lifetime_tax_gbp)} />
        <Stat label="Median years to ruin" value={result.median_years_to_ruin ?? 'never'} />
        <Stat label="Horizon" value={`${horizon} years`} />
        <Stat label="Engine time" value={`${result.elapsed_seconds}s`} />
      </div>
      <div className="rounded-lg border border-slate-200 bg-white p-5">
        <h2 className="text-lg font-semibold mb-2">Portfolio fan</h2>
        <FanChart yearly={result.yearly} height={420} showWithdrawal />
      </div>
    </>
  );
}

function Stat({
  label,
  value,
  accent,
}: {
  label: string;
  value: string | number;
  accent?: boolean;
}) {
  return (
    <div className="rounded-md border border-slate-200 bg-white p-3">
      <div className="text-xs uppercase tracking-wide text-slate-500">{label}</div>
      <div
        className={`text-xl font-semibold tabular-nums mt-1 ${
          accent ? 'text-emerald-700' : ''
        }`}
      >
        {value}
      </div>
    </div>
  );
}
