// ============================================================
// CALCULATOR PANELS — BMI / Vitals / Resus / Equipment.
// Data ported VERBATIM from public/js/calculators.js:
//   • VITALS_DATA   lines 1703-1831
//   • RESUS_MEDS     lines 1873-2050
//   • EQUIP_DATA     lines 2173-2228
// BMI math + LMS table live in shared/clinical/bmi.ts, verified
// byte-for-byte by calc-vectors.json (12 BMI cases).
// ============================================================

import { useState } from 'react';
import { computeBmi } from '@shared/clinical/bmi';
import type { Sex } from '@shared/clinical/fenton';
import { computeBp, type BpClassification } from '@shared/clinical/bp';

const card = 'rounded-lg border border-border bg-card p-5 space-y-3';
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 label = 'block text-xs font-medium text-muted-foreground';
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 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';

// ── BP Percentile (AAP 2017 Rosner splines) ────────────────
const BP_CLASS_STYLE: Record<BpClassification, { label: string; color: string; bg: string }> = {
  normal:   { label: 'Normal',              color: '#10b981', bg: '#d1fae5' },
  elevated: { label: 'Elevated',             color: '#f59e0b', bg: '#fef3c7' },
  stage1:   { label: 'Stage 1 Hypertension', color: '#f97316', bg: '#ffedd5' },
  stage2:   { label: 'Stage 2 Hypertension', color: '#ef4444', bg: '#fee2e2' },
};

export function BpPanel() {
  const [ageYears, setAgeYears] = useState('');
  const [sex, setSex] = useState<'female' | 'male'>('female');
  const [heightCm, setHeightCm] = useState('');
  const [sbp, setSbp] = useState('');
  const [dbp, setDbp] = useState('');
  const [error, setError] = useState('');
  const [result, setResult] = useState<ReturnType<typeof computeBp> | null>(null);

  function calc() {
    const a = Number.parseFloat(ageYears);
    const h = Number.parseFloat(heightCm);
    const s = Number.parseFloat(sbp);
    const d = Number.parseFloat(dbp);
    if (!Number.isFinite(a) || !Number.isFinite(h) || !Number.isFinite(s) || !Number.isFinite(d)) {
      setError('Fill in all fields.'); setResult(null); return;
    }
    if (a < 1 || a > 17) { setError('Age must be 1-17 years.'); setResult(null); return; }
    if (h < 50 || h > 200) { setError('Height must be 50-200 cm.'); setResult(null); return; }
    setError('');
    setResult(computeBp(a, sex, h, s, d));
  }

  const style = result ? BP_CLASS_STYLE[result.classification] : null;

  return (
    <section className={card} data-testid="calc-panel-bp">
      <h2 className="text-lg font-semibold">BP Percentile (AAP 2017)</h2>
      <div className="grid gap-3 sm:grid-cols-3">
        <div><label className={label}>Age (years)</label><input type="number" min="1" max="17" step="0.1" className={input} value={ageYears} onChange={(e) => setAgeYears(e.target.value)} data-testid="bp-age" /></div>
        <div><label className={label}>Sex</label><select className={input} value={sex} onChange={(e) => setSex(e.target.value as typeof sex)} data-testid="bp-sex"><option value="female">Female</option><option value="male">Male</option></select></div>
        <div><label className={label}>Height (cm)</label><input type="number" min="50" max="200" step="0.1" className={input} value={heightCm} onChange={(e) => setHeightCm(e.target.value)} data-testid="bp-height" /></div>
        <div><label className={label}>SBP (mmHg)</label><input type="number" min="50" max="220" step="1" className={input} value={sbp} onChange={(e) => setSbp(e.target.value)} data-testid="bp-sbp" /></div>
        <div><label className={label}>DBP (mmHg)</label><input type="number" min="30" max="150" step="1" className={input} value={dbp} onChange={(e) => setDbp(e.target.value)} data-testid="bp-dbp" /></div>
      </div>
      <div className="flex gap-2">
        <button type="button" onClick={calc} className={btnPrimary} data-testid="calc-bp-calculate">Calculate</button>
        <button type="button" onClick={() => { setAgeYears(''); setHeightCm(''); setSbp(''); setDbp(''); setResult(null); setError(''); }} className={btnGhost}>Clear</button>
      </div>
      {error && <div className={errorBox}>{error}</div>}
      {result && style && (
        <div
          className="rounded-lg p-4 space-y-2"
          style={{ background: style.bg, borderLeft: `4px solid ${style.color}` }}
          data-testid="calc-bp-result"
        >
          <div className="text-base font-bold" style={{ color: style.color }}>{style.label}</div>
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
            <div><span className="text-xs uppercase text-muted-foreground">Systolic</span><div className="font-semibold">{result.sysPercentile}th %ile</div><div className="text-xs text-muted-foreground">{BP_CLASS_STYLE[result.sysClass].label}</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">Diastolic</span><div className="font-semibold">{result.diaPercentile}th %ile</div><div className="text-xs text-muted-foreground">{BP_CLASS_STYLE[result.diaClass].label}</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">Height</span><div className="font-semibold">{result.heightPercentile.toFixed(0)}th %ile</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">Overall</span><div className="font-semibold">{style.label}</div></div>
          </div>
        </div>
      )}
      <div className="text-xs text-muted-foreground italic">
        Flynn JT et al. Clinical Practice Guideline for Screening and Management of High Blood Pressure in Children and Adolescents. Pediatrics 2017;140(3):e20171904.
      </div>
    </section>
  );
}

// ── BMI ─────────────────────────────────────────────────────
export function BmiPanel() {
  const [ageYr, setAgeYr] = useState('');
  const [ageMo, setAgeMo] = useState('');
  const [sex, setSex] = useState<Sex>('male');
  const [weight, setWeight] = useState('');
  const [height, setHeight] = useState('');
  const [error, setError] = useState('');
  const [result, setResult] = useState<ReturnType<typeof computeBmi> | null>(null);

  function calc() {
    const yr = Number.parseFloat(ageYr) || 0;
    const mo = Number.parseInt(ageMo, 10) || 0;
    const age = yr + mo / 12;
    const w = Number.parseFloat(weight);
    const h = Number.parseFloat(height);
    if (!age || !Number.isFinite(w) || w <= 0 || !Number.isFinite(h) || h <= 0) {
      setError('Fill in all fields.'); setResult(null); return;
    }
    if (age < 2 || age > 20) { setError('Age must be 2-20 years.'); setResult(null); return; }
    setError('');
    setResult(computeBmi(w, h, Math.round(age * 12), sex));
  }

  return (
    <section className={card} data-testid="calc-panel-bmi">
      <h2 className="text-lg font-semibold">BMI Percentile (CDC 2000)</h2>
      <div className="grid gap-3 sm:grid-cols-3">
        <div className="grid grid-cols-2 gap-2 sm:col-span-1">
          <div><label className={label}>Age (yr)</label><input type="number" min="2" max="20" step="0.1" className={input} value={ageYr} onChange={(e) => setAgeYr(e.target.value)} data-testid="bmi-age-yr" /></div>
          <div><label className={label}>Months</label><input type="number" min="0" max="11" className={input} value={ageMo} onChange={(e) => setAgeMo(e.target.value)} data-testid="bmi-age-mo" /></div>
        </div>
        <div><label className={label}>Sex</label><select className={input} value={sex} onChange={(e) => setSex(e.target.value as Sex)} data-testid="bmi-sex"><option value="male">Male</option><option value="female">Female</option></select></div>
        <div><label className={label}>Weight (kg)</label><input type="number" min="1" max="200" step="0.1" className={input} value={weight} onChange={(e) => setWeight(e.target.value)} data-testid="bmi-weight" /></div>
        <div className="sm:col-span-1"><label className={label}>Height (cm)</label><input type="number" min="50" max="220" step="0.1" className={input} value={height} onChange={(e) => setHeight(e.target.value)} data-testid="bmi-height" /></div>
      </div>
      <div className="flex gap-2">
        <button type="button" onClick={calc} className={btnPrimary} data-testid="calc-bmi-calculate">Calculate</button>
        <button type="button" onClick={() => { setAgeYr(''); setAgeMo(''); setWeight(''); setHeight(''); setResult(null); setError(''); }} className={btnGhost}>Clear</button>
      </div>
      {error && <div className={errorBox}>{error}</div>}
      {result && (
        <div
          className="rounded-lg p-4 space-y-2"
          style={{ background: result.classification.bg, borderLeft: `4px solid ${result.classification.color}` }}
          data-testid="calc-bmi-result"
        >
          <div className="text-base font-bold" style={{ color: result.classification.color }}>{result.classification.label}</div>
          <div className="text-sm">BMI {result.bmi.toFixed(1)} kg/m² — {result.percentile}th percentile</div>
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
            <div><span className="text-xs uppercase text-muted-foreground">BMI</span><div className="font-semibold">{result.bmi.toFixed(1)}</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">Percentile</span><div className="font-semibold">{result.percentile}th</div></div>
            <div><span className="text-xs uppercase text-muted-foreground">Z-Score</span><div className="font-semibold">{result.z.toFixed(2)}</div></div>
            {result.percentile >= 85 && <div><span className="text-xs uppercase text-muted-foreground">% of 95th</span><div className="font-semibold">{result.classification.pctOf95.toFixed(0)}%</div></div>}
          </div>
        </div>
      )}
      <div className="text-xs text-muted-foreground italic">CDC 2000 LMS tables · Kuczmarski et al. Vital Health Stat 11. 2002;(246).</div>
    </section>
  );
}

// ── Vitals ──────────────────────────────────────────────────
// Data ported verbatim from calculators.js:1703-1831.
interface VitalsEntry {
  label: string;
  hr: { awake: string; sleeping: string };
  rr: string;
  sbp: string;
  dbp: string;
  temp: string;
  weight: string;
  spo2: string;
  notes: string[];
}
const VITALS_DATA: Record<string, VitalsEntry> = {
  premie: { label: 'Premie', hr: { awake: '120-170', sleeping: '100-150' }, rr: '40-70', sbp: '55-75', dbp: '35-45', temp: '36.5-37.5', weight: '0.5-2.5 kg', spo2: '88-95% (target)',
    notes: ['HR and RR are highly variable and depend on gestational age', 'BP increases with gestational age and postnatal age', 'Target SpO2 88-95% to reduce retinopathy of prematurity risk', 'Temperature instability is common — use servo-controlled warmers', 'Bradycardia (<100 bpm) and apnea are common in premature infants'] },
  '0-3mo': { label: '0-3 Months', hr: { awake: '100-150', sleeping: '85-135' }, rr: '35-55', sbp: '65-85', dbp: '45-55', temp: '36.5-37.5', weight: '2.5-6 kg', spo2: '>95%',
    notes: ['HR normally increases with crying (up to 180-190 bpm) — this is physiologic', 'Periodic breathing (pauses <10 sec) is normal in neonates', 'Acrocyanosis (blue hands/feet) is normal; central cyanosis is not', 'BP is best measured in the right arm (pre-ductal) in neonates', 'Normal weight loss of 5-7% in first 3-5 days; regain by 10-14 days'] },
  '3-6mo': { label: '3-6 Months', hr: { awake: '90-120', sleeping: '75-110' }, rr: '30-45', sbp: '70-90', dbp: '50-65', temp: '36.5-37.5', weight: '5-8 kg', spo2: '>95%',
    notes: ['Expected weight gain: 20-30 g/day (150-200 g/week)', 'HR gradually decreases as vagal tone matures', 'RR >60 at rest may indicate lower respiratory tract disease', 'BP should be measured with appropriate cuff size (width 40% of arm circumference)'] },
  '6-12mo': { label: '6-12 Months', hr: { awake: '80-120', sleeping: '70-110' }, rr: '25-40', sbp: '80-100', dbp: '55-65', temp: '36.0-37.5', weight: '8-10 kg', spo2: '>95%',
    notes: ['Expected weight: triple birth weight by 12 months (~10 kg average)', 'Weight gain slows to ~10-15 g/day', 'Sinus arrhythmia (HR varies with breathing) is normal', 'Febrile tachycardia: HR increases ~10 bpm per 1 degree C above 37'] },
  '1-3yr': { label: '1-3 Years', hr: { awake: '70-110', sleeping: '60-100' }, rr: '20-30', sbp: '90-105', dbp: '55-70', temp: '36.0-37.5', weight: '10-15 kg', spo2: '>95%',
    notes: ['Expected weight gain: ~200-250 g/month (2-2.5 kg/year)', 'Tachycardia: HR >110 at rest warrants evaluation', 'Tachypnea: RR >30 at rest may indicate respiratory distress', 'BP screening begins at age 3 per AAP 2017 guidelines', 'Estimated weight: 2 x (age in years) + 8'] },
  '3-6yr': { label: '3-6 Years', hr: { awake: '65-110', sleeping: '55-100' }, rr: '20-25', sbp: '95-110', dbp: '60-75', temp: '36.0-37.5', weight: '14-20 kg', spo2: '>95%',
    notes: ['Annual BP screening recommended from age 3', 'Normal BP <90th percentile for age, sex, and height', 'Elevated BP: 90th to <95th percentile (or 120/80 if lower)', 'Estimated weight: 2 x (age in years) + 8', 'ETT size (uncuffed): (age/4) + 4'] },
  '6-12yr': { label: '6-12 Years', hr: { awake: '60-95', sleeping: '50-85' }, rr: '14-22', sbp: '100-120', dbp: '60-75', temp: '36.0-37.5', weight: '20-40 kg', spo2: '>95%',
    notes: ['Resting HR >95 or <60 warrants evaluation', 'BP should be measured at every clinical encounter', 'Stage 1 HTN: >=95th percentile on 3 separate occasions', 'Estimated weight: 3 x (age in years) + 7', 'ETT size (cuffed): (age/4) + 3.5'] },
  '>12yr': { label: '>12 Years', hr: { awake: '55-85', sleeping: '45-75' }, rr: '12-18', sbp: '110-135', dbp: '65-85', temp: '36.0-37.5', weight: '40-80 kg', spo2: '>95%',
    notes: ['Vital signs approach adult values', 'From age 13: use adult BP thresholds (AAP 2017)', 'Normal: <120/<80 mmHg; Elevated: 120-129/<80 mmHg', 'Stage 1 HTN: 130-139/80-89 mmHg; Stage 2 HTN: >=140/>=90 mmHg', 'Orthostatic vitals: measure lying, sitting, standing if dizzy', 'Athletic bradycardia (HR 45-60) may be normal in trained adolescents'] },
};
const VITALS_ORDER = ['premie', '0-3mo', '3-6mo', '6-12mo', '1-3yr', '3-6yr', '6-12yr', '>12yr'];

export function VitalsPanel() {
  const [key, setKey] = useState<string>('1-3yr');
  const v = VITALS_DATA[key];
  return (
    <section className={card} data-testid="calc-panel-vitals">
      <h2 className="text-lg font-semibold">Vital Signs by Age</h2>
      <div className="max-w-xs">
        <label className={label}>Age group</label>
        <select className={input} value={key} onChange={(e) => setKey(e.target.value)} data-testid="vitals-age-select">
          {VITALS_ORDER.map((k) => <option key={k} value={k}>{VITALS_DATA[k].label}</option>)}
        </select>
      </div>
      <div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm" data-testid="vitals-result">
        <div className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">Heart rate (awake)</div><div className="font-semibold">{v.hr.awake} bpm</div></div>
        <div className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">Heart rate (sleep)</div><div className="font-semibold">{v.hr.sleeping} bpm</div></div>
        <div className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">Respiratory rate</div><div className="font-semibold">{v.rr} /min</div></div>
        <div className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">SpO₂</div><div className="font-semibold">{v.spo2}</div></div>
        <div className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">SBP</div><div className="font-semibold">{v.sbp} mmHg</div></div>
        <div className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">DBP</div><div className="font-semibold">{v.dbp} mmHg</div></div>
        <div className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">Temperature</div><div className="font-semibold">{v.temp} °C</div></div>
        <div className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">Weight</div><div className="font-semibold">{v.weight}</div></div>
      </div>
      <div className="rounded-md bg-blue-50 dark:bg-blue-950/30 p-3 text-xs">
        <div className="font-semibold mb-1">Clinical notes</div>
        <ul className="list-disc pl-5 space-y-0.5">{v.notes.map((n, i) => <li key={i}>{n}</li>)}</ul>
      </div>
      <div className="text-xs text-muted-foreground italic">Harriet Lane Handbook 23rd ed · PALS · AAP 2017 BP guidelines.</div>
    </section>
  );
}

// ── Resus Meds ──────────────────────────────────────────────
// Data + math ported verbatim from calculators.js:1873-2050.
interface ResusResult { dose: string; extra: string; max: string }
interface ResusMed { name: string; indication: string; category: 'cardiac' | 'metabolic' | 'reversal'; route: string; calc: (w: number) => ResusResult }
const RESUS_MEDS: ResusMed[] = [
  { name: 'Adenosine', indication: 'SVT', category: 'cardiac', route: 'IV/IO rapid bolus',
    calc: (w) => { const d1 = +(w * 0.1).toFixed(2); const d2 = +(w * 0.2).toFixed(2); const d3 = +(w * 0.3).toFixed(2); return { dose: `${d1} mg (0.1 mg/kg)`, extra: `May repeat: ${Math.min(d2, 12)} mg (0.2 mg/kg), then ${Math.min(d3, 12)} mg (0.3 mg/kg)`, max: 'Max first dose 6 mg, max subsequent 12 mg' }; } },
  { name: 'Amiodarone', indication: 'VT / VF', category: 'cardiac', route: 'IV/IO',
    calc: (w) => { const d = +(w * 5).toFixed(1); return { dose: `${Math.min(d, 300)} mg (5 mg/kg)`, extra: 'No pulse: push undiluted. Pulse: over 20-60 min. Subsequent max 150 mg.', max: 'Max first 300 mg, max total 15 mg/kg/24hr or 2200 mg' }; } },
  { name: 'Atropine', indication: 'Bradycardia', category: 'cardiac', route: 'IV/IO/IM',
    calc: (w) => { const d = +(w * 0.02).toFixed(3); const ett = `${(w * 0.04).toFixed(3)}-${(w * 0.06).toFixed(3)}`; return { dose: `${Math.min(d, 0.5)} mg (0.02 mg/kg)`, extra: `ETT dose: ${ett} mg (0.04-0.06 mg/kg)`, max: 'Max single 0.5 mg, max total 1 mg' }; } },
  { name: 'Calcium Chloride 10%', indication: 'Hypocalcemia / Hyperkalemia', category: 'metabolic', route: 'IV/IO',
    calc: (w) => { const d = +(w * 20).toFixed(0); return { dose: `${Math.min(d, 1000)} mg (20 mg/kg)`, extra: 'Give slowly. Central line preferred.', max: 'Max 1 g (1000 mg)' }; } },
  { name: 'Calcium Gluconate 10%', indication: 'Hypocalcemia / Hyperkalemia', category: 'metabolic', route: 'IV/IO',
    calc: (w) => { const d = +(w * 60).toFixed(0); return { dose: `${Math.min(d, 3000)} mg (60 mg/kg)`, extra: 'Give slowly over 10-20 min with cardiac monitoring.', max: 'Max 3 g (3000 mg)' }; } },
  { name: 'Dextrose', indication: 'Hypoglycemia', category: 'metabolic', route: 'IV',
    calc: (w) => {
      const grams = `${+(w * 0.5).toFixed(1)}-${+(w * 1).toFixed(1)}`;
      let detail = '';
      if (w < 5)       detail = `D10W: ${(w * 5).toFixed(1)}-${(w * 10).toFixed(1)} mL (5-10 mL/kg)`;
      else if (w < 45) detail = `D25W: ${(w * 2).toFixed(1)}-${(w * 4).toFixed(1)} mL (2-4 mL/kg)`;
      else             detail = `D50W: ${(w * 1).toFixed(1)}-${(w * 2).toFixed(1)} mL (1-2 mL/kg)`;
      return { dose: `${grams} g (0.5-1 g/kg)`, extra: detail, max: 'Max 25 g' };
    } },
  { name: 'Epinephrine', indication: 'Pulseless arrest / Anaphylaxis', category: 'cardiac', route: 'IV/IO/IM/ETT',
    calc: (w) => { const iv = +(w * 0.01).toFixed(3); const ivVol = +(w * 0.1).toFixed(2); const ett = +(w * 0.1).toFixed(2); const im = +(w * 0.01).toFixed(3);
      return { dose: `${Math.min(iv, 1)} mg IV/IO (0.01 mg/kg of 0.1 mg/mL = ${Math.min(ivVol, 10)} mL) q3-5 min`, extra: `ETT: ${Math.min(ett, 2.5)} mg (0.1 mg/kg of 1 mg/mL). Anaphylaxis IM: ${Math.min(im, 0.5)} mg (0.01 mg/kg)`, max: 'Max IV 1 mg, max ETT 2.5 mg, max IM 0.5 mg' }; } },
  { name: 'Hydrocortisone', indication: 'Adrenal crisis', category: 'metabolic', route: 'IV/IM/IO',
    calc: (w) => { const d = +(w * 2).toFixed(1); return { dose: `${Math.min(d, 100)} mg (2 mg/kg)`, extra: 'Stress dosing for adrenal insufficiency.', max: 'Max 100 mg' }; } },
  { name: 'Insulin (Regular)', indication: 'Hyperkalemia', category: 'metabolic', route: 'IV',
    calc: (w) => { const d = +(w * 0.1).toFixed(2); const dex = +(w * 0.5).toFixed(1); return { dose: `${Math.min(d, 5)} units (0.1 units/kg)`, extra: `Give with ${dex} g/kg dextrose (0.5 g/kg). Monitor glucose closely.`, max: 'Max 5 units' }; } },
  { name: 'Lidocaine', indication: 'Antiarrhythmic', category: 'cardiac', route: 'IV/IO',
    calc: (w) => { const d = +(w * 1).toFixed(1); const ett = `${(w * 2).toFixed(1)}-${(w * 3).toFixed(1)}`; return { dose: `${Math.min(d, 100)} mg (1 mg/kg)`, extra: `ETT: ${ett} mg (2-3 mg/kg). May repeat q5 min.`, max: 'Max 100 mg/dose, max total 3 mg/kg' }; } },
  { name: 'Magnesium Sulfate', indication: 'Torsades de Pointes', category: 'cardiac', route: 'IV/IO',
    calc: (w) => { const d = +(w * 50).toFixed(0); return { dose: `${Math.min(d, 2000)} mg (50 mg/kg)`, extra: 'Give over 10-20 min (faster if pulseless).', max: 'Max 2 g (2000 mg)' }; } },
  { name: 'Naloxone', indication: 'Opioid overdose', category: 'reversal', route: 'IV/IO/IM/IN/ETT',
    calc: (w) => { const partial = `${+(w * 0.001).toFixed(4)}-${+(w * 0.005).toFixed(4)}`; const full = +(w * 0.1).toFixed(3); return { dose: `Partial: ${partial} mg (0.001-0.005 mg/kg)`, extra: `Full reversal: ${Math.min(full, 2)} mg (0.1 mg/kg)`, max: 'Max partial first dose 0.1 mg, max full 2 mg' }; } },
  { name: 'Sodium Bicarbonate', indication: 'Metabolic acidosis', category: 'metabolic', route: 'IV/IO',
    calc: (w) => { const d = +(w * 1).toFixed(1); return { dose: `${Math.min(d, 50)} mEq (1 mEq/kg)`, extra: w < 10 ? 'Dilute to 0.5 mEq/mL (use 4.2% solution) for neonates/small infants.' : 'Use 8.4% solution (1 mEq/mL).', max: 'Max 50 mEq' }; } },
];
const catColor: Record<ResusMed['category'], string> = { cardiac: '#ef4444', metabolic: '#3b82f6', reversal: '#10b981' };
const catLabel: Record<ResusMed['category'], string> = { cardiac: 'Cardiac', metabolic: 'Metabolic', reversal: 'Reversal' };

export function ResusPanel() {
  const [weight, setWeight] = useState('');
  const wt = Number.parseFloat(weight);
  const valid = Number.isFinite(wt) && wt > 0;

  return (
    <section className={card} data-testid="calc-panel-resus">
      <h2 className="text-lg font-semibold">Resus Medications</h2>
      <div className="max-w-xs">
        <label className={label}>Weight (kg)</label>
        <input type="number" min="0.5" max="100" step="0.1" className={input} value={weight} onChange={(e) => setWeight(e.target.value)} data-testid="resus-weight" />
      </div>
      {!valid ? <p className="text-xs text-destructive">Enter weight (kg) to see doses.</p> : (
        <>
          <div className="text-sm font-semibold">Doses for {wt} kg patient</div>
          <div className="flex gap-3 flex-wrap text-xs">
            {(['cardiac', 'metabolic', 'reversal'] as const).map((c) => (
              <span key={c} className="inline-flex items-center gap-1"><span className="w-2.5 h-2.5 rounded-full" style={{ background: catColor[c] }} />{catLabel[c]}</span>
            ))}
          </div>
          <div className="grid gap-3 grid-cols-1 md:grid-cols-2 lg:grid-cols-3" data-testid="resus-result">
            {RESUS_MEDS.map((med) => {
              const r = med.calc(wt);
              const color = catColor[med.category];
              return (
                <div key={med.name} className="rounded-lg border bg-card overflow-hidden" style={{ borderColor: color + '55' }}>
                  <div className="px-3 py-2 border-b" style={{ background: color + '10', borderColor: color + '22' }}>
                    <div className="text-sm font-bold" style={{ color }}>{med.name}</div>
                    <div className="text-xs text-muted-foreground">{med.indication}</div>
                  </div>
                  <div className="p-3 text-sm space-y-1">
                    <div><strong>Dose:</strong> {r.dose}</div>
                    <div className="text-xs text-muted-foreground">{r.extra}</div>
                    <div className="text-xs text-muted-foreground"><strong>Max:</strong> {r.max}</div>
                    <div className="text-xs text-muted-foreground"><strong>Route:</strong> {med.route}</div>
                  </div>
                </div>
              );
            })}
          </div>
          <div className="rounded-md bg-amber-50 dark:bg-amber-950/30 p-3 text-xs text-amber-900 dark:text-amber-100">
            <strong>Disclaimer:</strong> Always verify doses against institutional protocols and current guidelines.
          </div>
        </>
      )}
    </section>
  );
}

// ── Equipment ───────────────────────────────────────────────
// Data ported verbatim from calculators.js:2173-2228.
interface EquipEntry {
  label: string;
  bvm: string; nasal: string; oral: string; blade: string;
  ett: string; lma: string; glidescope: string;
  iv: string; cvl: string; ngt: string; chest: string; foley: string;
}
const EQUIP_DATA: Record<string, EquipEntry> = {
  premie:   { label: 'Premie (1-3 kg)',         bvm: 'Infant',                nasal: '12 Fr',      oral: 'Infant',       blade: 'Miller 0',                ett: '2.5-3.0', lma: '1',    glidescope: '1',       iv: '22-24 ga', cvl: '3 Fr',    ngt: '5 Fr',    chest: '10-12 Fr', foley: '6 Fr' },
  newborn:  { label: 'Newborn (2-4 kg)',        bvm: 'Infant',                nasal: '14-16 Fr',   oral: 'Small 50 mm',  blade: 'Miller 0',                ett: '3.0-3.5', lma: '1',    glidescope: '1',       iv: '22-24 ga', cvl: '3-4 Fr',  ngt: '5-8 Fr',  chest: '10-12 Fr', foley: '6 Fr' },
  '6mo':    { label: '6 months (6-8 kg)',       bvm: 'Infant',                nasal: '14-16 Fr',   oral: 'Small 60 mm',  blade: 'Miller 1',                ett: '3.5',     lma: '1.5',  glidescope: '2',       iv: '20-24 ga', cvl: '4 Fr',    ngt: '8 Fr',    chest: '12-18 Fr', foley: '8 Fr' },
  '1yr':    { label: '1 year (10 kg)',           bvm: 'Small child',           nasal: '14-18 Fr',   oral: 'Small 60 mm',  blade: 'Miller 1 / MAC 2',        ett: '4.0',     lma: '2',    glidescope: '2',       iv: '20-24 ga', cvl: '4-5 Fr',  ngt: '10 Fr',   chest: '16-20 Fr', foley: '8 Fr' },
  '2-3yr':  { label: '2-3 years (12-16 kg)',    bvm: 'Small child',           nasal: '14-18 Fr',   oral: 'Small 70 mm',  blade: 'Miller 1 / MAC 2',        ett: '4.0-4.5', lma: '2',    glidescope: '2',       iv: '18-22 ga', cvl: '4-5 Fr',  ngt: '10-12 Fr', chest: '16-24 Fr', foley: '8 Fr' },
  '4-6yr':  { label: '4-6 years (20-25 kg)',    bvm: 'Child',                  nasal: '16-20 Fr',   oral: 'Small 70-80 mm', blade: 'Miller 2 / MAC 2',      ett: '4.5-5.0', lma: '2.5',  glidescope: '3',       iv: '18-22 ga', cvl: '5 Fr',    ngt: '12-14 Fr', chest: '20-28 Fr', foley: '8 Fr' },
  '7-10yr': { label: '7-10 years (25-35 kg)',    bvm: 'Child / Small adult',   nasal: '18-22 Fr',   oral: 'Medium 80-90 mm', blade: 'Miller 2 / MAC 2',     ett: '5.5-6.0', lma: '2.5-3', glidescope: '3',      iv: '18-22 ga', cvl: '5 Fr',    ngt: '12-14 Fr', chest: '20-32 Fr', foley: '8 Fr' },
  '11-15yr': { label: '11-15 years (40-50 kg)',  bvm: 'Adult',                  nasal: '22-36 Fr',   oral: 'Medium 90 mm', blade: 'Miller 2 / MAC 3',       ett: '6.0-6.5', lma: '3',    glidescope: '3 or 4',  iv: '18-20 ga', cvl: '7 Fr',    ngt: '14-18 Fr', chest: '28-38 Fr', foley: '10 Fr' },
  '16yr':   { label: '16+ years (>50 kg)',      bvm: 'Adult',                  nasal: '22-36 Fr',   oral: 'Medium 90 mm', blade: 'Miller 2 / MAC 3',       ett: '7.0-8.0', lma: '4',    glidescope: '3 or 4',  iv: '18-20 ga', cvl: '7 Fr',    ngt: '14-18 Fr', chest: '28-42 Fr', foley: '12 Fr' },
};
const EQUIP_ORDER = ['premie', 'newborn', '6mo', '1yr', '2-3yr', '4-6yr', '7-10yr', '11-15yr', '16yr'];

export function EquipmentPanel() {
  const [key, setKey] = useState('1yr');
  const e = EQUIP_DATA[key];
  const rows: Array<[string, string]> = [
    ['BVM',              e.bvm],
    ['Nasopharyngeal',   e.nasal],
    ['Oropharyngeal',    e.oral],
    ['Laryngoscope',     e.blade],
    ['ETT',              e.ett],
    ['LMA',              e.lma],
    ['Glidescope',       e.glidescope],
    ['IV',               e.iv],
    ['Central line',     e.cvl],
    ['NG tube',          e.ngt],
    ['Chest tube',       e.chest],
    ['Foley',            e.foley],
  ];
  return (
    <section className={card} data-testid="calc-panel-equipment">
      <h2 className="text-lg font-semibold">Equipment Sizing</h2>
      <div className="max-w-xs">
        <label className={label}>Age / weight band</label>
        <select className={input} value={key} onChange={(e2) => setKey(e2.target.value)} data-testid="equip-age-select">
          {EQUIP_ORDER.map((k) => <option key={k} value={k}>{EQUIP_DATA[k].label}</option>)}
        </select>
      </div>
      <div className="grid grid-cols-2 md:grid-cols-3 gap-3 text-sm" data-testid="equip-result">
        {rows.map(([lbl, val]) => (
          <div key={lbl} className="rounded-md bg-muted/40 p-3"><div className="text-xs uppercase text-muted-foreground">{lbl}</div><div className="font-semibold">{val}</div></div>
        ))}
      </div>
      <div className="text-xs text-muted-foreground italic">Harriet Lane Handbook · PALS · Broselow cross-reference.</div>
    </section>
  );
}
