// ============================================================
// CALCULATORS — incremental React port.
// Low-risk pure formulas run here; high-risk table-driven calculators
// stay in the vanilla viewer until legacy vectors land.
//
// WHY this is gated on test vectors (from the migration checkpoint):
//   • AAP 2017 BP percentile uses Rosner quantile splines with long
//     hard-coded coefficient arrays.
//   • Fenton 2013 LMS preterm growth carries 210 validated cases.
//   • AAP 2022 bilirubin phototherapy + exchange: per-week risk
//     curves, 1190 validated cases.
//   • Bhutani nomogram risk zones.
//   • APLS + Best Guess weight-for-age.
//
// Per the checkpoint: "An LLM will sometimes 'simplify' a long array
// of numbers and silently break it — don't let that happen." Every
// calculator needs a JSON vector file (~20 known inputs + expected
// outputs captured from public/js/calculators.js) before its React
// port lands, and the port must match every vector byte-for-byte.
//
// Pill order + labels match public/components/calculators.html.
// ============================================================

import { useState } from 'react';
import {
  calculateGcs,
  calculateMostellerBsa,
  calculateWeightBasedDose,
} from '@shared/clinical/calculators';
import { classifyBhutani, classifyAapBili, type BiliRisk } from '@shared/clinical/bilirubin';
import { fentonWeightForAge, classifySizeForAge, type Sex } from '@shared/clinical/fenton';

const card = 'rounded-lg border border-border bg-card p-5 space-y-3';
const btnPrimary = 'rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium';
const btnGhost = 'rounded-md border border-border bg-background px-4 py-2 text-sm font-medium hover:bg-muted';
const field = 'space-y-1';
const label = 'block text-xs font-medium text-muted-foreground';
const input = 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring';
const resultBox = 'rounded-lg border border-border bg-muted/40 p-4';
const errorBox = 'rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30 dark:text-red-200';

interface Pill {
  id: string;
  label: string;
  summary: string;
  source: string; // where the formulas live
  ported?: boolean;
}

const PILLS: Pill[] = [
  { id: 'bp',        label: 'BP Percentile',       summary: 'AAP 2017 age/height/sex-adjusted BP percentiles (Rosner quantile splines).', source: 'AAP 2017 (Flynn) — Rosner splines' },
  { id: 'bmi',       label: 'BMI Percentile',      summary: 'BMI-for-age (CDC 2000 z-score tables).', source: 'CDC 2000 LMS' },
  { id: 'growth',    label: 'Growth Charts',       summary: 'Fenton 2013 preterm weight-for-GA with Z-score + percentile + SGA/AGA/LGA classification.', source: 'Fenton 2013 LMS', ported: true },
  { id: 'bili',      label: 'Bilirubin',           summary: 'AAP 2022 phototherapy + exchange thresholds and Bhutani nomogram risk zones.', source: 'AAP 2022 (Kemper) + Bhutani 1999', ported: true },
  { id: 'vitals',    label: 'Vital Signs',         summary: 'Normal HR / RR / BP ranges by age.', source: 'PALS + AHA reference' },
  { id: 'bsa',       label: 'Body Surface Area',   summary: 'Mosteller body surface area formula.', source: 'Mosteller 1987', ported: true },
  { id: 'dose',      label: 'Weight-Based Dosing', summary: 'Generic mg/kg dosing with optional max-dose cap and concentration conversion.', source: 'Legacy calculator formula', ported: true },
  { id: 'resus',     label: 'Resus Meds',          summary: 'Code-cart dosing (epinephrine, amiodarone, atropine, etc.).', source: 'PALS' },
  { id: 'gcs',       label: 'GCS',                 summary: 'Child/adult and infant Glasgow Coma Scale variants.', source: 'Teasdale + pediatric modification', ported: true },
  { id: 'equipment', label: 'Equipment',           summary: 'ETT size, blade, NG, Foley, suction by age/weight.', source: 'PALS + Broselow cross-reference' },
];

function parseOptionalNumber(value: string): number | null {
  if (!value.trim()) return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function FormField({
  id,
  labelText,
  value,
  onChange,
  min,
  max,
  step = '0.1',
  placeholder,
}: {
  id: string;
  labelText: string;
  value: string;
  onChange: (value: string) => void;
  min?: string;
  max?: string;
  step?: string;
  placeholder?: string;
}) {
  return (
    <div className={field}>
      <label htmlFor={id} className={label}>{labelText}</label>
      <input
        id={id}
        type="number"
        min={min}
        max={max}
        step={step}
        value={value}
        onChange={(event) => onChange(event.target.value)}
        placeholder={placeholder}
        className={input}
      />
    </div>
  );
}

function BsaPanel() {
  const [weight, setWeight] = useState('');
  const [height, setHeight] = useState('');
  const [result, setResult] = useState<number | null>(null);
  const [error, setError] = useState('');

  function calculate() {
    const next = calculateMostellerBsa(Number(weight), Number(height));
    if (next == null) {
      setError('Enter a valid weight and height.');
      setResult(null);
      return;
    }
    setError('');
    setResult(next);
  }

  function clear() {
    setWeight('');
    setHeight('');
    setResult(null);
    setError('');
  }

  return (
    <section className={card} data-testid="calc-panel-bsa">
      <h2 className="text-lg font-semibold">Body Surface Area</h2>
      <p className="text-sm text-muted-foreground">
        Mosteller formula: BSA (m2) = sqrt(height(cm) x weight(kg) / 3600).
      </p>
      <div className="grid gap-3 sm:grid-cols-2">
        <FormField id="react-bsa-weight" labelText="Weight (kg)" value={weight} onChange={setWeight} min="1" max="200" placeholder="20" />
        <FormField id="react-bsa-height" labelText="Height (cm)" value={height} onChange={setHeight} min="30" max="220" placeholder="110" />
      </div>
      <div className="flex gap-2">
        <button type="button" onClick={calculate} className={btnPrimary} data-testid="calc-bsa-calculate">Calculate</button>
        <button type="button" onClick={clear} className={btnGhost}>Clear</button>
      </div>
      {error ? <div className={errorBox}>{error}</div> : null}
      {result == null ? null : (
        <div className={resultBox} data-testid="calc-bsa-result">
          <div className="text-xs uppercase tracking-wide text-muted-foreground">Mosteller BSA</div>
          <div className="text-2xl font-semibold">{result.toFixed(3)} m²</div>
          <div className="text-sm text-muted-foreground">{weight} kg, {height} cm</div>
        </div>
      )}
    </section>
  );
}

function DosePanel() {
  const [weight, setWeight] = useState('');
  const [dosePerKg, setDosePerKg] = useState('');
  const [frequency, setFrequency] = useState('1');
  const [maxDose, setMaxDose] = useState('');
  const [concentration, setConcentration] = useState('');
  const [result, setResult] = useState<ReturnType<typeof calculateWeightBasedDose>>(null);
  const [error, setError] = useState('');

  function calculate() {
    const next = calculateWeightBasedDose({
      weightKg: Number(weight),
      dosePerKg: Number(dosePerKg),
      frequencyPerDay: Number(frequency),
      maxSingleDoseMg: parseOptionalNumber(maxDose),
      concentrationMgPerMl: parseOptionalNumber(concentration),
    });
    if (next == null) {
      setError('Enter a valid weight, mg/kg dose, and frequency.');
      setResult(null);
      return;
    }
    setError('');
    setResult(next);
  }

  function clear() {
    setWeight('');
    setDosePerKg('');
    setFrequency('1');
    setMaxDose('');
    setConcentration('');
    setResult(null);
    setError('');
  }

  return (
    <section className={card} data-testid="calc-panel-dose">
      <h2 className="text-lg font-semibold">Weight-Based Dosing</h2>
      <p className="text-sm text-muted-foreground">
        Generic mg/kg calculator. Always verify medication-specific dosing against formulary and local policy.
      </p>
      <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
        <FormField id="react-dose-weight" labelText="Patient Weight (kg)" value={weight} onChange={setWeight} min="1" max="200" placeholder="15" />
        <FormField id="react-dose-per-kg" labelText="Dose (mg/kg)" value={dosePerKg} onChange={setDosePerKg} min="0.01" step="0.01" placeholder="10" />
        <div className={field}>
          <label htmlFor="react-dose-frequency" className={label}>Frequency</label>
          <select id="react-dose-frequency" value={frequency} onChange={(event) => setFrequency(event.target.value)} className={input}>
            <option value="1">Once daily</option>
            <option value="2">Twice daily (BID)</option>
            <option value="3">Three times daily (TID)</option>
            <option value="4">Four times daily (QID)</option>
            <option value="6">Every 4 hours (Q4H)</option>
          </select>
        </div>
        <FormField id="react-dose-max" labelText="Max single dose (mg, optional)" value={maxDose} onChange={setMaxDose} min="0" step="1" placeholder="500" />
        <FormField id="react-dose-concentration" labelText="Concentration (mg/mL, optional)" value={concentration} onChange={setConcentration} min="0" placeholder="40" />
      </div>
      <div className="flex gap-2">
        <button type="button" onClick={calculate} className={btnPrimary} data-testid="calc-dose-calculate">Calculate</button>
        <button type="button" onClick={clear} className={btnGhost}>Clear</button>
      </div>
      {error ? <div className={errorBox}>{error}</div> : null}
      {result == null ? null : (
        <div className={resultBox} data-testid="calc-dose-result">
          <div className="grid gap-3 sm:grid-cols-3">
            <div>
              <div className="text-xs uppercase tracking-wide text-muted-foreground">Single Dose</div>
              <div className="text-xl font-semibold">{result.singleDoseMg.toFixed(1)} mg</div>
              {result.capped ? <div className="text-xs text-red-600">Capped at max dose</div> : null}
            </div>
            <div>
              <div className="text-xs uppercase tracking-wide text-muted-foreground">Daily Total</div>
              <div className="text-xl font-semibold">{result.dailyDoseMg.toFixed(1)} mg/day</div>
              <div className="text-xs text-muted-foreground">x {result.frequencyPerDay}/day</div>
            </div>
            <div>
              <div className="text-xs uppercase tracking-wide text-muted-foreground">Volume</div>
              <div className="text-xl font-semibold">{result.volumeMl == null ? 'n/a' : `${result.volumeMl.toFixed(1)} mL`}</div>
              <div className="text-xs text-muted-foreground">per dose</div>
            </div>
          </div>
        </div>
      )}
    </section>
  );
}

const GCS_OPTIONS = {
  child: {
    eye: [
      ['4', '4 - Spontaneous'],
      ['3', '3 - To speech'],
      ['2', '2 - To pain'],
      ['1', '1 - None'],
    ],
    verbal: [
      ['5', '5 - Oriented'],
      ['4', '4 - Confused'],
      ['3', '3 - Inappropriate words'],
      ['2', '2 - Incomprehensible sounds'],
      ['1', '1 - None'],
    ],
    motor: [
      ['6', '6 - Obeys commands'],
      ['5', '5 - Localizes pain'],
      ['4', '4 - Withdraws to pain'],
      ['3', '3 - Abnormal flexion'],
      ['2', '2 - Abnormal extension'],
      ['1', '1 - None'],
    ],
  },
  infant: {
    eye: [
      ['4', '4 - Spontaneous'],
      ['3', '3 - To speech/sound'],
      ['2', '2 - To painful stimuli'],
      ['1', '1 - None'],
    ],
    verbal: [
      ['5', '5 - Coos/babbles'],
      ['4', '4 - Irritable cry'],
      ['3', '3 - Cries to pain'],
      ['2', '2 - Moans to pain'],
      ['1', '1 - None'],
    ],
    motor: [
      ['6', '6 - Normal spontaneous movement'],
      ['5', '5 - Withdraws to touch'],
      ['4', '4 - Withdraws to pain'],
      ['3', '3 - Abnormal flexion'],
      ['2', '2 - Abnormal extension'],
      ['1', '1 - None'],
    ],
  },
} as const;

function GcsSelect({
  id,
  labelText,
  value,
  options,
  onChange,
}: {
  id: string;
  labelText: string;
  value: string;
  options: readonly (readonly [string, string])[];
  onChange: (value: string) => void;
}) {
  return (
    <div className={field}>
      <label htmlFor={id} className={label}>{labelText}</label>
      <select id={id} value={value} onChange={(event) => onChange(event.target.value)} className={input}>
        {options.map(([optionValue, text]) => (
          <option key={optionValue} value={optionValue}>{text}</option>
        ))}
      </select>
    </div>
  );
}

function GcsPanel() {
  const [scale, setScale] = useState<'child' | 'infant'>('child');
  const [eye, setEye] = useState('4');
  const [verbal, setVerbal] = useState('5');
  const [motor, setMotor] = useState('6');
  const result = calculateGcs(Number(eye), Number(verbal), Number(motor));
  const options = GCS_OPTIONS[scale];

  function switchScale(next: 'child' | 'infant') {
    setScale(next);
    setEye('4');
    setVerbal('5');
    setMotor('6');
  }

  return (
    <section className={card} data-testid="calc-panel-gcs">
      <h2 className="text-lg font-semibold">Glasgow Coma Scale</h2>
      <p className="text-sm text-muted-foreground">
        Select responses to calculate child/adult or infant-modified GCS. Total score 3-15.
      </p>
      <div className="flex flex-wrap gap-2">
        <button
          type="button"
          onClick={() => switchScale('child')}
          className={'px-3 py-1.5 rounded-full text-xs font-medium border ' + (scale === 'child' ? 'bg-primary text-primary-foreground border-primary' : 'bg-muted border-border')}
        >
          Child / Adult
        </button>
        <button
          type="button"
          onClick={() => switchScale('infant')}
          className={'px-3 py-1.5 rounded-full text-xs font-medium border ' + (scale === 'infant' ? 'bg-primary text-primary-foreground border-primary' : 'bg-muted border-border')}
        >
          Infant
        </button>
      </div>
      <div className="grid gap-3 sm:grid-cols-3">
        <GcsSelect id="react-gcs-eye" labelText="Eye Opening" value={eye} options={options.eye} onChange={setEye} />
        <GcsSelect id="react-gcs-verbal" labelText="Verbal Response" value={verbal} options={options.verbal} onChange={setVerbal} />
        <GcsSelect id="react-gcs-motor" labelText="Motor Response" value={motor} options={options.motor} onChange={setMotor} />
      </div>
      {result == null ? null : (
        <div className={resultBox} data-testid="calc-gcs-result">
          <div className="text-xs uppercase tracking-wide text-muted-foreground">{scale === 'infant' ? 'Infant-modified GCS' : 'Child / adult GCS'}</div>
          <div className="text-3xl font-semibold">GCS: {result.total}/15</div>
          <div className="text-sm text-muted-foreground">{result.severity}</div>
          <div className="mt-2 text-xs text-muted-foreground">Interpretation: 13-15 Mild, 9-12 Moderate, 3-8 Severe/Coma.</div>
        </div>
      )}
    </section>
  );
}

function LegacyPanel({ pill }: { pill: Pill }) {
  return (
    <section className={card} data-testid={'calc-panel-' + pill.id}>
      <h2 className="text-lg font-semibold">{pill.label}</h2>
      <p className="text-sm text-muted-foreground">{pill.summary}</p>
      <div className="rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2">
        <p className="text-amber-900 dark:text-amber-100">
          <strong>Source of truth:</strong> {pill.source}.
        </p>
        <p className="text-amber-900 dark:text-amber-100">
          This calculator runs in the legacy viewer. A React port is gated on capturing test vectors
          from the vanilla implementation so the numerical output can be verified byte-for-byte —
          the migration checkpoint specifically flags this class of data as the one an LLM is most
          likely to silently simplify.
        </p>
      </div>
      <a href="/#calculators" className={btnPrimary + ' inline-block'}>
        Open in legacy viewer
      </a>
    </section>
  );
}

function BiliPanel() {
  const [mode, setMode] = useState<'aap' | 'bhutani'>('aap');
  const [ga, setGa] = useState('38');
  const [hours, setHours] = useState('');
  const [tsb, setTsb] = useState('');
  const [risk, setRisk] = useState<BiliRisk>('low');
  const [aapResult, setAapResult] = useState<ReturnType<typeof classifyAapBili> | null>(null);
  const [bhutResult, setBhutResult] = useState<ReturnType<typeof classifyBhutani> | null>(null);
  const [error, setError] = useState('');

  function calc() {
    const hoursNum = Number(hours);
    const tsbNum = Number(tsb);
    if (!Number.isFinite(hoursNum) || !Number.isFinite(tsbNum) || hoursNum <= 0 || tsbNum <= 0) {
      setError('Enter hours of life and TSB (mg/dL).');
      setAapResult(null);
      setBhutResult(null);
      return;
    }
    setError('');
    if (mode === 'aap') {
      const gaNum = Number(ga);
      if (!Number.isFinite(gaNum) || gaNum < 35) {
        setError('AAP 2022 thresholds apply to GA ≥35 weeks.');
        setAapResult(null);
        return;
      }
      setAapResult(classifyAapBili(gaNum, hoursNum, tsbNum, risk));
      setBhutResult(null);
    } else {
      setBhutResult(classifyBhutani(hoursNum, tsbNum));
      setAapResult(null);
    }
  }

  const statusColor = aapResult
    ? aapResult.status === 'Above Exchange'   ? 'text-red-800 bg-red-100'
    : aapResult.status === 'Above Phototherapy' ? 'text-red-700 bg-red-50'
    : 'text-green-700 bg-green-50'
    : '';
  const zoneColor = bhutResult
    ? bhutResult.zone === 'High-Risk'          ? 'text-red-800 bg-red-100'
    : bhutResult.zone === 'High-Intermediate' ? 'text-orange-700 bg-orange-50'
    : bhutResult.zone === 'Low-Intermediate'  ? 'text-amber-700 bg-amber-50'
    : 'text-green-700 bg-green-50'
    : '';

  return (
    <section className={card} data-testid="calc-panel-bili">
      <h2 className="text-lg font-semibold">Bilirubin</h2>
      <div className="flex gap-2">
        <button type="button" onClick={() => setMode('aap')}      className={'px-3 py-1 rounded text-xs font-medium ' + (mode === 'aap' ? 'bg-primary text-primary-foreground' : 'bg-muted')} data-testid="bili-mode-aap">AAP 2022 Phototherapy</button>
        <button type="button" onClick={() => setMode('bhutani')} className={'px-3 py-1 rounded text-xs font-medium ' + (mode === 'bhutani' ? 'bg-primary text-primary-foreground' : 'bg-muted')} data-testid="bili-mode-bhutani">Bhutani Nomogram</button>
      </div>

      <div className="grid gap-3 sm:grid-cols-2">
        {mode === 'aap' && (
          <>
            <div className={field}>
              <label htmlFor="bili-ga" className={label}>GA (weeks)</label>
              <select id="bili-ga" className={input} value={ga} onChange={(e) => setGa(e.target.value)}>
                {[35, 36, 37, 38, 39, 40].map((g) => <option key={g} value={g}>{g}{g === 40 ? '+' : ''}</option>)}
              </select>
            </div>
            <div className={field}>
              <label htmlFor="bili-risk" className={label}>Neurotoxicity risk</label>
              <select id="bili-risk" className={input} value={risk} onChange={(e) => setRisk(e.target.value as BiliRisk)}>
                <option value="low">No risk factors</option>
                <option value="medium">With risk factors</option>
              </select>
            </div>
          </>
        )}
        <FormField id="bili-hours" labelText="Age (hours)" value={hours} onChange={setHours} min="0" max="336" placeholder="48" />
        <FormField id="bili-tsb"   labelText="TSB (mg/dL)" value={tsb}   onChange={setTsb}   min="0" max="50" placeholder="15" />
      </div>
      <div className="flex gap-2">
        <button type="button" className={btnPrimary} onClick={calc} data-testid="calc-bili-calculate">Calculate</button>
        <button type="button" className={btnGhost} onClick={() => { setHours(''); setTsb(''); setAapResult(null); setBhutResult(null); setError(''); }}>Clear</button>
      </div>
      {error && <div className={errorBox}>{error}</div>}

      {aapResult && (
        <div className={resultBox + ' space-y-2'} data-testid="calc-bili-aap-result">
          <div className={'inline-block px-2 py-1 rounded text-sm font-bold ' + statusColor}>{aapResult.status}</div>
          <div className="text-sm">TSB {tsb} mg/dL at {hours} hours of life (GA {ga}w {risk === 'medium' ? 'with' : 'without'} risk factors)</div>
          <div className="grid grid-cols-2 gap-3 text-sm">
            <div><span className="text-xs uppercase text-muted-foreground">Phototherapy</span><div className="font-semibold">{aapResult.photoThreshold.toFixed(1)} mg/dL</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">Exchange</span><div className="font-semibold text-red-800">{aapResult.exchangeThreshold.toFixed(1)} mg/dL</div></div>
          </div>
          <div className="text-xs text-muted-foreground italic">AAP 2022 CPG (Kemper et al.). Always use clinical judgment.</div>
        </div>
      )}
      {bhutResult && (
        <div className={resultBox + ' space-y-2'} data-testid="calc-bili-bhutani-result">
          <div className={'inline-block px-2 py-1 rounded text-sm font-bold ' + zoneColor}>{bhutResult.zone} Zone</div>
          <div className="text-sm">TSB {tsb} mg/dL at {hours} hours of life</div>
          <div className="grid grid-cols-3 gap-3 text-sm">
            <div><span className="text-xs uppercase text-muted-foreground">40th %ile</span><div className="font-semibold">{bhutResult.p40.toFixed(1)}</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">75th %ile</span><div className="font-semibold">{bhutResult.p75.toFixed(1)}</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">95th %ile</span><div className="font-semibold">{bhutResult.p95.toFixed(1)}</div></div>
          </div>
          <div className="text-xs text-muted-foreground italic">Bhutani 1999 hour-specific risk nomogram for infants ≥35 weeks GA.</div>
        </div>
      )}
    </section>
  );
}

function GrowthPanel() {
  const [sex, setSex] = useState<Sex>('male');
  const [ga, setGa] = useState('');
  const [weight, setWeight] = useState('');
  const [result, setResult] = useState<ReturnType<typeof fentonWeightForAge> | null>(null);
  const [error, setError] = useState('');

  function calc() {
    const gaNum = Number(ga);
    const wtNum = Number(weight);
    if (!Number.isFinite(gaNum) || !Number.isFinite(wtNum) || gaNum < 22 || gaNum > 50 || wtNum <= 0) {
      setError('Enter GA (22-50 weeks) and weight (grams).');
      setResult(null);
      return;
    }
    setError('');
    setResult(fentonWeightForAge(gaNum, wtNum, sex));
  }

  const classification = result ? classifySizeForAge(result.percentile) : null;
  const classColor = classification === 'SGA' ? 'text-orange-700 bg-orange-50'
    : classification === 'LGA' ? 'text-amber-700 bg-amber-50'
    : 'text-green-700 bg-green-50';

  return (
    <section className={card} data-testid="calc-panel-growth">
      <h2 className="text-lg font-semibold">Fenton 2013 Preterm Growth</h2>
      <p className="text-sm text-muted-foreground">Weight-for-gestational-age Z-score + percentile + SGA/AGA/LGA classification.</p>
      <div className="grid gap-3 sm:grid-cols-3">
        <div className={field}>
          <label htmlFor="fenton-sex" className={label}>Sex</label>
          <select id="fenton-sex" className={input} value={sex} onChange={(e) => setSex(e.target.value as Sex)}>
            <option value="male">Male</option>
            <option value="female">Female</option>
          </select>
        </div>
        <FormField id="fenton-ga"     labelText="GA (weeks)"   value={ga}     onChange={setGa}     min="22" max="50"   step="0.1" placeholder="32" />
        <FormField id="fenton-weight" labelText="Weight (g)"  value={weight} onChange={setWeight} min="200" max="7000" step="10"  placeholder="1500" />
      </div>
      <div className="flex gap-2">
        <button type="button" className={btnPrimary} onClick={calc} data-testid="calc-fenton-calculate">Calculate</button>
        <button type="button" className={btnGhost} onClick={() => { setGa(''); setWeight(''); setResult(null); setError(''); }}>Clear</button>
      </div>
      {error && <div className={errorBox}>{error}</div>}
      {result && classification && (
        <div className={resultBox + ' space-y-2'} data-testid="calc-fenton-result">
          <div className={'inline-block px-2 py-1 rounded text-sm font-bold ' + classColor}>{classification}</div>
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
            <div><span className="text-xs uppercase text-muted-foreground">Percentile</span><div className="font-semibold">{result.percentile.toFixed(1)}%</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">Z-score</span><div className="font-semibold">{result.z.toFixed(2)}</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">Median (M)</span><div className="font-semibold">{Math.round(result.M)} g</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">L / S</span><div className="font-mono text-xs">{result.L.toFixed(3)} / {result.S.toFixed(3)}</div></div>
          </div>
          <div className="text-xs text-muted-foreground italic">Fenton TR, Kim JH. Systematic review — revised Fenton growth chart for preterm infants. BMC Pediatr 2013;13:59.</div>
        </div>
      )}
    </section>
  );
}

function ActivePanel({ pill }: { pill: Pill }) {
  if (pill.id === 'bsa') return <BsaPanel />;
  if (pill.id === 'dose') return <DosePanel />;
  if (pill.id === 'gcs') return <GcsPanel />;
  if (pill.id === 'bili') return <BiliPanel />;
  if (pill.id === 'growth') return <GrowthPanel />;
  return <LegacyPanel pill={pill} />;
}

export default function Calculators() {
  const [active, setActive] = useState<string>(PILLS[0].id);
  const pill = PILLS.find((p) => p.id === active) ?? PILLS[0];

  return (
    <div className="max-w-5xl mx-auto p-6 space-y-4">
      <header>
        <h1 className="text-2xl font-semibold">Calculators</h1>
        <p className="text-sm text-muted-foreground">
          Pediatric calculators — BP percentiles, bilirubin thresholds, growth, dosing, equipment sizing.
          Simple pure-formula calculators run in React now; high-risk table-driven calculators remain
          legacy-gated until vectors are captured.
        </p>
      </header>

      <div className="flex flex-wrap gap-2" data-testid="calc-subnav">
        {PILLS.map((p) => (
          <button
            key={p.id}
            type="button"
            onClick={() => setActive(p.id)}
            className={
              'px-3 py-1.5 rounded-full text-xs font-medium border transition-colors ' +
              (active === p.id
                ? 'bg-primary text-primary-foreground border-primary'
                : 'bg-muted hover:bg-muted/80 border-border')
            }
            data-testid={'calc-pill-' + p.id}
          >
            {p.label}{p.ported ? <span className="ml-1 text-[10px] opacity-80">React</span> : null}
          </button>
        ))}
      </div>

      <ActivePanel pill={pill} />
    </div>
  );
}
