// ============================================================
// Unit tests for public/js/calc-math.js (pediatric dose math).
// Run with: npm test     (alias for: node --test test/)
// ============================================================
const { test } = require('node:test');
const assert = require('node:assert/strict');
const M = require('../public/js/calc-math.js');

// ── estimateWeightFromAgeMonths ────────────────────────────────
test('APLS 0-12 mo: 6 mo -> 7 kg', () => {
  const r = M.estimateWeightFromAgeMonths(6);
  assert.equal(r.weight, 7);                    // 0.5*6 + 4 = 7
  assert.match(r.formulaLabel, /0-12 mo/);
});

test('APLS 1-5 yr: 3 yr -> 14 kg', () => {
  const r = M.estimateWeightFromAgeMonths(36);
  assert.equal(r.weight, 14);                   // 2*3 + 8 = 14
  assert.match(r.formulaLabel, /1-5 yr/);
});

test('APLS 6-12 yr: 8 yr -> 31 kg', () => {
  const r = M.estimateWeightFromAgeMonths(96);
  assert.equal(r.weight, 31);                   // 3*8 + 7 = 31
  assert.match(r.formulaLabel, /6-12 yr/);
});

test('Best Guess 14 yr -> 56 kg (switches away from APLS at >=13 yr)', () => {
  const r = M.estimateWeightFromAgeMonths(168);
  assert.equal(r.weight, 56);                   // 4 * 14 = 56
  assert.match(r.formulaLabel, /Best Guess/);
});

test('APLS and Best Guess both reported', () => {
  const r = M.estimateWeightFromAgeMonths(48);
  assert.equal(r.all.apls, 16);                 // 2*4 + 8
  assert.equal(r.all.bestGuess, 18);            // 2*(4+5)
});

test('Null/negative age returns null', () => {
  assert.equal(M.estimateWeightFromAgeMonths(null), null);
  assert.equal(M.estimateWeightFromAgeMonths(-5), null);
});

// ── Parkland ────────────────────────────────────────────────────
test('Parkland 20kg, 25% TBSA -> 2000 mL total, 1000 first 8h', () => {
  const p = M.parkland(20, 25);
  assert.equal(p.totalMl, 2000);               // 4 * 20 * 25
  assert.equal(p.first8Ml, 1000);
  assert.equal(p.next16Ml, 1000);
  assert.equal(p.rateFirst8, 125);             // 1000 / 8
  assert.equal(p.rateNext16, 63);              // 1000 / 16 = 62.5 -> 63
});

test('Parkland invalid inputs return null', () => {
  assert.equal(M.parkland(0, 25), null);
  assert.equal(M.parkland(20, 0), null);
  assert.equal(M.parkland(null, 25), null);
});

// ── Maintenance 4-2-1 ──────────────────────────────────────────
test('Maintenance: 8 kg -> 32 mL/hr', () => { assert.equal(M.maintenance421(8), 32); });
test('Maintenance: 15 kg -> 50 mL/hr', () => { assert.equal(M.maintenance421(15), 50); });
test('Maintenance: 25 kg -> 65 mL/hr', () => { assert.equal(M.maintenance421(25), 65); });
test('Maintenance: 70 kg -> 110 mL/hr', () => { assert.equal(M.maintenance421(70), 110); });

// ── PRAM ────────────────────────────────────────────────────────
test('PRAM all zero -> Mild (0)', () => {
  const r = M.pramScore(0, 0, 0, 0, 0);
  assert.equal(r.total, 0); assert.equal(r.severity, 'Mild');
});
test('PRAM total 5 -> Moderate', () => {
  const r = M.pramScore(1, 2, 0, 1, 1);
  assert.equal(r.total, 5); assert.equal(r.severity, 'Moderate');
});
test('PRAM total 10 -> Severe', () => {
  const r = M.pramScore(2, 2, 2, 2, 2);
  assert.equal(r.total, 10); assert.equal(r.severity, 'Severe');
});

// ── Westley ─────────────────────────────────────────────────────
test('Westley 2 -> Mild', () => { assert.equal(M.westleyScore(0, 0, 1, 0, 1).severity, 'Mild'); });
test('Westley 4 -> Moderate', () => { assert.equal(M.westleyScore(0, 0, 2, 1, 1).severity, 'Moderate'); });
test('Westley 8 -> Severe', () => { assert.equal(M.westleyScore(0, 4, 2, 1, 1).severity, 'Severe'); });
test('Westley 12 -> Impending respiratory failure', () => { assert.equal(M.westleyScore(5, 4, 0, 1, 2).severity, 'Impending respiratory failure'); });

// ── Epinephrine (anaphylaxis vs arrest) ─────────────────────────
test('Epi anaphylaxis IM: 20 kg -> 0.2 mg (1:1000)', () => {
  const r = M.epiAnaphylaxisIM(20);
  assert.equal(r.doseMg, 0.2);
  assert.equal(r.volumeMl_1in1000, 0.2);      // 1 mg/mL concentration
});

test('Epi anaphylaxis IM: max 0.5 mg for >=50 kg', () => {
  assert.equal(M.epiAnaphylaxisIM(80).doseMg, 0.5);
});

test('Epi arrest IV: 10 kg -> 0.1 mg, 1 mL of 1:10,000', () => {
  const r = M.epiArrestIV(10);
  assert.equal(r.doseMg, 0.1);
  assert.equal(r.volumeMl_1in10000, 1);        // 0.1 mg/mL concentration
});

test('Epi arrest IV: max 1 mg for >=100 kg', () => {
  assert.equal(M.epiArrestIV(120).doseMg, 1);
});

// ── NRP epi ──────────────────────────────────────────────────────
test('NRP epi: 3 kg newborn -> 0.03-0.09 mg IV (0.3-0.9 mL of 1:10,000)', () => {
  const r = M.nrpEpiIV(3);
  assert.equal(r.lowMg, 0.03);
  assert.equal(r.highMg, 0.09);
  assert.equal(r.lowMl, 0.3);
  assert.equal(r.highMl, 0.9);
});

// ── RSI drugs ────────────────────────────────────────────────────
test('Ketamine RSI 20 kg -> 30-40 mg', () => {
  const r = M.rsiKetamine(20);
  assert.equal(r.lowMg, 30);
  assert.equal(r.highMg, 40);
});

test('Rocuronium 25 kg -> 30 mg (1.2 mg/kg)', () => {
  assert.equal(M.rsiRocuronium(25).mg, 30);
});

test('Succinylcholine dose/kg: <10 kg = 2 mg/kg; >=10 kg = 1.5 mg/kg', () => {
  assert.equal(M.rsiSuccinylcholine(8).perKg, 2);
  assert.equal(M.rsiSuccinylcholine(20).perKg, 1.5);
  assert.equal(M.rsiSuccinylcholine(20).mg, 30);       // 20*1.5
});

// ── Min systolic BP ─────────────────────────────────────────────
test('Min SBP: 5 yr -> 80', () => { assert.equal(M.minSBP(5), 80); });
test('Min SBP: infant (<1 yr) -> 70', () => { assert.equal(M.minSBP(0.5), 70); });
test('Min SBP: 12 yr -> 90', () => { assert.equal(M.minSBP(12), 90); });

// ── ETT sizing ──────────────────────────────────────────────────
test('ETT 4 yr: uncuffed 5.0, cuffed 4.5, depth 15', () => {
  const r = M.ettSize(4);
  assert.equal(r.uncuffedMm, 5);
  assert.equal(r.cuffedMm, 4.5);
  assert.equal(r.depthCm, 15);
});

// ── Lund-Browder TBSA ───────────────────────────────────────────
test('Lund-Browder: full head infant = 18%', () => {
  assert.equal(M.lundBrowderTBSA('infant', { head: 100 }), 18);
});
test('Lund-Browder: full head adult = 7%', () => {
  assert.equal(M.lundBrowderTBSA('adult', { head: 100 }), 7);
});
test('Lund-Browder: full body adds to ~100% (infant) — tolerance for published rounding', () => {
  const all = {};
  Object.keys(M.LB).forEach((k) => { all[k] = 100; });
  const t = M.lundBrowderTBSA('infant', all);
  // Published charts typically sum to 99-100% depending on head % (18 vs 19 at 0 yr vs 1 yr).
  assert.ok(t >= 98 && t <= 100.5, 'expected 98-100.5, got ' + t);
});
test('Lund-Browder: 50% of both thighs young child = 8%', () => {
  // young (1-5) thigh = 8% each, so 50% of each = 4% + 4% = 8%
  assert.equal(M.lundBrowderTBSA('young', { r_thigh: 50, l_thigh: 50 }), 8);
});
test('Lund-Browder: invalid bracket returns null', () => {
  assert.equal(M.lundBrowderTBSA('bogus', { head: 50 }), null);
});

// ── drugs.json schema ───────────────────────────────────────────
// Asserts the JSON is loadable and that each of the 5 extracted
// bedside sections exists with a non-empty drugs array.
test('drugs.json: schema loads with all 5 extracted sections + non-empty drugs', () => {
  const fs = require('node:fs');
  const path = require('node:path');
  const raw = fs.readFileSync(path.join(__dirname, '..', 'public', 'data', 'drugs.json'), 'utf8');
  const j = JSON.parse(raw);
  assert.equal(j.version, '1.0');
  assert.ok(j.sections, 'top-level sections object present');
  ['anaphylaxis', 'sedation', 'agitation', 'antiemetics', 'seizure'].forEach((key) => {
    const sec = j.sections[key];
    assert.ok(sec, `section ${key} present`);
    assert.ok(Array.isArray(sec.drugs) && sec.drugs.length > 0, `${key}.drugs non-empty`);
  });
});
