// dosing.sy -- Clinical dosing calculations as native Scyla.
// Body size, renal/hepatic adjustment, pediatric, IV drips,
// electrolyte corrections, and drug-specific dosing.
//
// Sources:
//   DiPiro JT et al. Pharmacotherapy, 12th ed, McGraw-Hill, 2023.
//   Winter ME. Basic Clinical Pharmacokinetics, 6th ed, 2018.
//
// Marjorie McCubbins + Aether, April 2026
// Patent pending: 64/034,536


// =========================================================
// BODY SIZE CALCULATIONS
// =========================================================
//
// Accurate body size metrics are the foundation of individualized
// drug dosing. BSA determines chemotherapy doses. IBW determines
// aminoglycoside and ventilator settings. ABW provides a middle
// ground for obese patients. BMI screens for obesity-related
// dosing adjustments.
//
// DiPiro 12th ed, Ch 1: Clinical Pharmacokinetics and
// Pharmacodynamics, pp 3-22.

// Body surface area by the Mosteller formula (1987).
// BSA (m^2) = sqrt(height_cm * weight_kg / 3600)
// Preferred for simplicity and accuracy comparable to DuBois.
// Mosteller RD. N Engl J Med 317:1098, 1987.
fn bsa_mosteller(height_cm: Float, weight_kg: Float) -> Float {
    return sqrt(height_cm * weight_kg / 3600.0)
}

// Body surface area by the DuBois and DuBois formula (1916).
// BSA (m^2) = 0.007184 * height_cm^0.725 * weight_kg^0.425
// Original formula derived from only nine subjects but validated
// extensively. Still the reference standard for regulatory filings.
// DuBois D, DuBois EF. Arch Intern Med 17:863, 1916.
fn bsa_dubois(height_cm: Float, weight_kg: Float) -> Float {
    return 0.007184 * pow(height_cm, 0.725) * pow(weight_kg, 0.425)
}

// Ideal body weight for males (Devine formula, 1974).
// IBW (kg) = 50 + 2.3 * (height_inches - 60)
// Used for aminoglycoside dosing, ventilator tidal volumes,
// and Cockcroft-Gault when patient is obese.
// Devine BJ. Drug Intell Clin Pharm 8:650, 1974.
fn ibw_male(height_inches: Float) -> Float {
    return 50.0 + 2.3 * (height_inches - 60.0)
}

// Ideal body weight for females (Devine formula, 1974).
// IBW (kg) = 45.5 + 2.3 * (height_inches - 60)
fn ibw_female(height_inches: Float) -> Float {
    return 45.5 + 2.3 * (height_inches - 60.0)
}

// Adjusted body weight for obese patients.
// ABW = IBW + 0.4 * (actual_weight - IBW)
// Used when actual body weight exceeds IBW by more than 20-30%.
// Recommended for aminoglycoside and vancomycin dosing in obesity.
// Winter 6th ed, Ch 1, pp 8-10.
fn abw(ibw_kg: Float, actual_weight_kg: Float) -> Float {
    return ibw_kg + 0.4 * (actual_weight_kg - ibw_kg)
}

// Body mass index.
// BMI = weight_kg / height_m^2
// Underweight: < 18.5, Normal: 18.5-24.9, Overweight: 25-29.9,
// Obese class I: 30-34.9, II: 35-39.9, III: >= 40.
fn bmi(weight_kg: Float, height_m: Float) -> Float {
    return weight_kg / pow(height_m, 2.0)
}

// BMI classification string.
fn bmi_classification(bmi_val: Float) -> String {
    if bmi_val < 18.5 { return "Underweight (BMI < 18.5). Consider malnutrition screening." }
    if bmi_val < 25.0 { return "Normal weight (BMI 18.5-24.9)." }
    if bmi_val < 30.0 { return "Overweight (BMI 25-29.9). Monitor metabolic parameters." }
    if bmi_val < 35.0 { return "Obese class I (BMI 30-34.9). Consider ABW for dosing." }
    if bmi_val < 40.0 { return "Obese class II (BMI 35-39.9). Use ABW for aminoglycosides." }
    return "Obese class III (BMI >= 40). Bariatric dosing considerations required."
}


// =========================================================
// RENAL DOSING
// =========================================================
//
// Renal function determines clearance of drugs eliminated by
// the kidney. CrCl and eGFR guide dose reductions to prevent
// accumulation and toxicity.
//
// DiPiro 12th ed, Ch 45: Acute Kidney Injury.
// Winter 6th ed, Ch 9: Creatinine Clearance.

// Cockcroft-Gault equation for creatinine clearance (1976).
// CrCl (mL/min) = ((140 - age) * weight_kg) / (72 * Scr)
// Multiply result by 0.85 for females.
// Uses ACTUAL body weight per original derivation.
// Cockcroft DW, Gault MH. Nephron 16:31, 1976.
fn cockcroft_gault(age: Float, weight_kg: Float, scr: Float, is_female: Bool) -> Float {
    let crcl = ((140.0 - age) * weight_kg) / (72.0 * scr)
    if is_female { return crcl * 0.85 }
    return crcl
}

// Cockcroft-Gault using ideal body weight.
// Preferred for obese patients (actual > 120% IBW) per FDA
// drug labeling guidance for renal dose adjustments.
fn cockcroft_gault_ibw(age: Float, ibw_kg: Float, scr: Float, is_female: Bool) -> Float {
    let crcl = ((140.0 - age) * ibw_kg) / (72.0 * scr)
    if is_female { return crcl * 0.85 }
    return crcl
}

// CKD-EPI 2021 equation for eGFR (race-free).
// eGFR = 142 * min(Scr/kappa, 1)^alpha * max(Scr/kappa, 1)^(-1.200)
//        * 0.9938^age (* 1.012 if female)
// kappa = 0.7 (female), 0.9 (male)
// alpha = -0.241 (female), -0.302 (male)
// Race-free per 2021 NKF-ASN Task Force recommendation.
// Inker LA et al. N Engl J Med 385:1737, 2021.
fn ckd_epi_2021(age: Float, scr: Float, is_female: Bool) -> Float {
    if is_female {
        let kappa = 0.7
        let ratio = scr / kappa
        let min_term = ratio
        if min_term > 1.0 { min_term = 1.0 }
        let max_term = ratio
        if max_term < 1.0 { max_term = 1.0 }
        return 142.0 * pow(min_term, 0.0 - 0.241) * pow(max_term, 0.0 - 1.200) * pow(0.9938, age) * 1.012
    }
    let kappa = 0.9
    let ratio = scr / kappa
    let min_term = ratio
    if min_term > 1.0 { min_term = 1.0 }
    let max_term = ratio
    if max_term < 1.0 { max_term = 1.0 }
    return 142.0 * pow(min_term, 0.0 - 0.302) * pow(max_term, 0.0 - 1.200) * pow(0.9938, age)
}

// Schwartz equation for pediatric eGFR.
// eGFR = k * height_cm / Scr
// k = 0.413 (bedside Schwartz, all pediatric patients)
// Original Schwartz (1976) used k=0.55 for children, 0.70 for
// adolescent males, but the bedside version (2009) uses 0.413
// with enzymatic creatinine assays (IDMS-traceable).
// Schwartz GJ et al. J Am Soc Nephrol 20:629, 2009.
fn schwartz_pediatric(height_cm: Float, scr: Float) -> Float {
    return 0.413 * height_cm / scr
}

// Renal dose adjustment using CrCl cutoff tiers.
// Given a normal dose and CrCl, returns the adjusted dose.
// cutoff_high/cutoff_mid/cutoff_low define the CrCl thresholds.
// frac_high/frac_mid/frac_low/frac_below define the dose fractions
// for each tier. Example: CrCl >= 50 -> 100%, 30-49 -> 75%,
// range 15-29 -> 50%, < 15 -> 25%.
// DiPiro 12th ed, drug-specific appendices.
fn renal_dose_adjustment(normal_dose: Float, crcl: Float, cutoff_high: Float, cutoff_mid: Float, cutoff_low: Float, frac_high: Float, frac_mid: Float, frac_low: Float, frac_below: Float) -> Float {
    if crcl >= cutoff_high { return normal_dose * frac_high }
    if crcl >= cutoff_mid { return normal_dose * frac_mid }
    if crcl >= cutoff_low { return normal_dose * frac_low }
    return normal_dose * frac_below
}

// CKD staging by GFR for dosing context.
fn ckd_stage(gfr: Float) -> String {
    if gfr >= 90.0 { return "G1: Normal or high (>= 90). No renal dose adjustment needed." }
    if gfr >= 60.0 { return "G2: Mildly decreased (60-89). Adjust nephrotoxic drugs." }
    if gfr >= 45.0 { return "G3a: Mild-moderate decrease (45-59). Reduce renally cleared drugs." }
    if gfr >= 30.0 { return "G3b: Moderate-severe decrease (30-44). Significant reductions." }
    if gfr >= 15.0 { return "G4: Severely decreased (15-29). Major reductions. Avoid nephrotoxins." }
    return "G5: Kidney failure (< 15). Dialysis dosing. Many drugs contraindicated."
}


// =========================================================
// HEPATIC DOSING
// =========================================================
//
// Hepatic impairment reduces drug metabolism (CYP450, UGT),
// decreases albumin (altered protein binding), and increases
// portal hypertension (altered distribution). Child-Pugh and
// MELD scores quantify severity for dose adjustments.
//
// DiPiro 12th ed, Ch 40: Portal Hypertension and Cirrhosis.
// Winter 6th ed, Ch 12: Hepatic Clearance.

// Child-Pugh score calculation.
// Each parameter scored 1-3 points. Total 5-15.
// Encephalopathy: 0=none(1pt), 1-2=mild(2pt), 3-4=severe(3pt)
// Ascites: 0=none(1pt), 1=mild(2pt), 2=moderate-severe(3pt)
// Bilirubin mg/dL: <2(1pt), 2-3(2pt), >3(3pt)
// Albumin g/dL: >3.5(1pt), 2.8-3.5(2pt), <2.8(3pt)
// INR: <1.7(1pt), 1.7-2.3(2pt), >2.3(3pt)
// Pugh RNH et al. Br J Surg 60:646, 1973.
fn child_pugh_score(encephalopathy_grade: Int, ascites_grade: Int, bilirubin: Float, albumin: Float, inr: Float) -> Int {
    let score = 0

    // Encephalopathy: grade 0 = 1 point, grade 1-2 = 2 points, grade 3-4 = 3 points
    if encephalopathy_grade == 0 { score = score + 1 }
    if encephalopathy_grade == 1 { score = score + 2 }
    if encephalopathy_grade == 2 { score = score + 2 }
    if encephalopathy_grade == 3 { score = score + 3 }
    if encephalopathy_grade == 4 { score = score + 3 }

    // Ascites: 0 = none(1pt), 1 = mild(2pt), 2 = moderate-severe(3pt)
    if ascites_grade == 0 { score = score + 1 }
    if ascites_grade == 1 { score = score + 2 }
    if ascites_grade == 2 { score = score + 3 }

    // Bilirubin (mg/dL)
    if bilirubin < 2.0 { score = score + 1 }
    if bilirubin >= 2.0 {
        if bilirubin <= 3.0 { score = score + 2 }
    }
    if bilirubin > 3.0 { score = score + 3 }

    // Albumin (g/dL)
    if albumin > 3.5 { score = score + 1 }
    if albumin >= 2.8 {
        if albumin <= 3.5 { score = score + 2 }
    }
    if albumin < 2.8 { score = score + 3 }

    // INR
    if inr < 1.7 { score = score + 1 }
    if inr >= 1.7 {
        if inr <= 2.3 { score = score + 2 }
    }
    if inr > 2.3 { score = score + 3 }

    return score
}

// Child-Pugh class from total score.
fn child_pugh_class(score: Int) -> String {
    if score <= 6 { return "Class A (5-6): Mild impairment. Most drugs tolerated at standard doses." }
    if score <= 9 { return "Class B (7-9): Moderate impairment. Reduce hepatically cleared drugs 25-50%." }
    return "Class C (10-15): Severe impairment. Reduce 50-75% or avoid. Many drugs contraindicated."
}

// MELD score (Model for End-Stage Liver Disease).
// MELD = 3.78 * ln(bilirubin) + 11.2 * ln(INR) + 9.57 * ln(creatinine) + 6.43
// All lab values capped at minimum of 1.0 to prevent ln(0).
// Used for transplant prioritization and mortality prediction.
// Kamath PS et al. Hepatology 33:464, 2001.
fn meld_score(bilirubin: Float, inr: Float, creatinine: Float) -> Float {
    let bili = bilirubin
    if bili < 1.0 { bili = 1.0 }
    let inr_val = inr
    if inr_val < 1.0 { inr_val = 1.0 }
    let cr = creatinine
    if cr < 1.0 { cr = 1.0 }
    // Cap creatinine at 4.0 per UNOS policy
    if cr > 4.0 { cr = 4.0 }
    return 3.78 * ln(bili) + 11.2 * ln(inr_val) + 9.57 * ln(cr) + 6.43
}

// MELD-Na (sodium-adjusted MELD).
// MELD-Na = MELD + 1.32 * (137 - Na) - 0.033 * MELD * (137 - Na)
// Sodium capped at 125-137 mEq/L per UNOS.
// Hyponatremia in cirrhosis independently predicts mortality.
// Kim WR et al. Hepatology 47:1363, 2008.
fn meld_na(meld_val: Float, sodium: Float) -> Float {
    let na = sodium
    if na < 125.0 { na = 125.0 }
    if na > 137.0 { na = 137.0 }
    let correction = 1.32 * (137.0 - na) - 0.033 * meld_val * (137.0 - na)
    let result = meld_val + correction
    // MELD-Na cannot exceed 40
    if result > 40.0 { return 40.0 }
    return result
}


// =========================================================
// PEDIATRIC DOSING
// =========================================================
//
// Pediatric patients are NOT small adults. Drug metabolism,
// body composition, organ maturity, and protein binding differ
// significantly. These rules provide approximations when
// pediatric-specific data is unavailable. BSA-based dosing
// is the most accurate of the approximation methods.
//
// DiPiro 12th ed, Ch 108: Pediatric Pharmacotherapy.
// Winter 6th ed, Ch 16: Pediatric Patients.

// Clark's rule for pediatric dose estimation.
// Child dose = adult_dose * (child_weight_lbs / 150)
// Simple weight-based approximation. Less accurate than
// BSA-based methods. Use only when better data unavailable.
fn clark_rule(adult_dose: Float, child_weight_lbs: Float) -> Float {
    return adult_dose * child_weight_lbs / 150.0
}

// Young's rule for children over 2 years.
// Child dose = adult_dose * age_years / (age_years + 12)
// Age-based approximation. Does not account for weight variation
// within age groups. Use BSA-based dosing when possible.
fn young_rule(adult_dose: Float, child_age_years: Float) -> Float {
    return adult_dose * child_age_years / (child_age_years + 12.0)
}

// Fried's rule for infants (< 2 years).
// Child dose = adult_dose * age_months / 150
// Very rough approximation. Only for non-critical medications
// when no pediatric dosing data exists.
fn fried_rule(adult_dose: Float, child_age_months: Float) -> Float {
    return adult_dose * child_age_months / 150.0
}

// BSA-based pediatric dosing (most accurate approximation).
// Child dose = adult_dose * (child_BSA / 1.73)
// Standard adult BSA is 1.73 m^2. This method correlates
// better with organ size and metabolic capacity than weight
// or age alone. Standard for oncology dosing.
fn bsa_dosing(adult_dose: Float, child_bsa: Float) -> Float {
    return adult_dose * child_bsa / 1.73
}


// =========================================================
// AMINOGLYCOSIDE DOSING
// =========================================================
//
// Aminoglycosides exhibit concentration-dependent killing and
// a significant post-antibiotic effect. Extended-interval
// (once-daily) dosing maximizes peak:MIC ratio while allowing
// drug-free periods to reduce nephrotoxicity and ototoxicity.
//
// DiPiro 12th ed, Ch 113: Antimicrobial Regimen Selection.
// Winter 6th ed, Ch 5: Aminoglycosides.

// Traditional gentamicin dosing (multiple daily).
// Returns dose in mg and interval in hours.
// Traditional target: peak 5-10 mcg/mL, trough < 2 mcg/mL.
// Standard dose: 1.5-2.5 mg/kg q8h, adjusted for renal function.
// Winter 6th ed, Ch 5, pp 131-168.
struct GentamicinDose {
    dose_mg: Float,
    interval_hours: Float,
    notes: String,
}

fn gentamicin_traditional(weight_kg: Float, crcl: Float) -> GentamicinDose {
    // Standard dose 2.0 mg/kg
    let dose = 2.0 * weight_kg

    // Interval based on CrCl
    // CrCl >= 80: q8h, 60-79: q12h, 40-59: q18h, 20-39: q24h, < 20: q48h
    if crcl >= 80.0 {
        return GentamicinDose { dose_mg: dose, interval_hours: 8.0, notes: "CrCl >= 80: q8h. Monitor peak and trough at steady-state (3rd dose)." }
    }
    if crcl >= 60.0 {
        return GentamicinDose { dose_mg: dose, interval_hours: 12.0, notes: "CrCl 60-79: q12h. Monitor peak and trough. Watch for nephrotoxicity." }
    }
    if crcl >= 40.0 {
        return GentamicinDose { dose_mg: dose, interval_hours: 18.0, notes: "CrCl 40-59: q18h. Increased nephrotoxicity risk. Monitor SCr daily." }
    }
    if crcl >= 20.0 {
        return GentamicinDose { dose_mg: dose, interval_hours: 24.0, notes: "CrCl 20-39: q24h. High nephrotoxicity risk. Consider alternative agents." }
    }
    return GentamicinDose { dose_mg: dose, interval_hours: 48.0, notes: "CrCl < 20: q48h. Severe renal impairment. Monitor levels closely. Consider dialysis dosing." }
}

// Vancomycin AUC-based dosing (2020 ASHP/IDSA guidelines).
// Daily dose = target_AUC * clearance
// Target AUC/MIC 400-600 mcg*h/mL (assuming MIC = 1 mcg/mL).
// AUC-based monitoring superior to trough-only monitoring for
// reducing nephrotoxicity while maintaining efficacy.
// Rybak MJ et al. Am J Health Syst Pharm 77:835, 2020.
fn vancomycin_auc_dosing(target_auc: Float, clearance_l_hr: Float) -> Float {
    return target_auc * clearance_l_hr
}

// Estimate vancomycin AUC from trough concentration.
// AUC = (dose / (Vd * ke)) * (1 - exp(-ke * tau)) / (1 - exp(-ke * tau))
// Simplified: AUC_ss,24h = daily_dose / CL
// More precise two-level AUC calculation preferred clinically.
// This single-trough estimate uses population PK approach:
// ke = CL / Vd, CL estimated from trough.
// Winter 6th ed, Ch 6: Vancomycin, pp 169-212.
fn vancomycin_trough_to_auc(trough: Float, dose_mg: Float, interval_hr: Float, vd_liters: Float, ke: Float) -> Float {
    // AUC per interval = (dose/Vd) / ke * (1 - exp(-ke*tau)) approximation
    // For steady state: AUC_tau = dose / CL where CL = ke * Vd
    let clearance = ke * vd_liters
    let daily_doses = 24.0 / interval_hr
    let daily_dose = dose_mg * daily_doses
    // AUC over 24 hours at steady state
    let auc_24 = daily_dose / clearance
    return auc_24
}


// =========================================================
// IV DRIP CALCULATIONS
// =========================================================
//
// Intravenous infusion rate calculations are among the most
// critical nursing and pharmacy computations. Errors in drip
// rate can cause fatal overdoses or subtherapeutic levels.
// Every equation here is dimensional analysis.
//
// DiPiro 12th ed, Ch 1: Clinical Pharmacokinetics.

// Simple IV drip rate in mL/hr.
// rate = total_volume_mL / infusion_hours
fn drip_rate_ml_hr(total_volume_ml: Float, hours: Float) -> Float {
    return total_volume_ml / hours
}

// IV drip rate in drops per minute.
// gtt/min = (volume_mL * drop_factor) / (hours * 60)
// Common drop factors: 10 (macro), 15 (macro), 20 (macro), 60 (micro).
fn drip_rate_gtt_min(total_volume_ml: Float, hours: Float, drop_factor: Float) -> Float {
    return (total_volume_ml * drop_factor) / (hours * 60.0)
}

// Convert mcg/kg/min dose to mL/hr infusion rate.
// mL/hr = (dose_mcg_kg_min * weight_kg * 60) / concentration_mcg_per_mL
// Used for: dopamine, dobutamine, epinephrine, norepinephrine,
// nitroprusside, nitroglycerin, milrinone.
fn mcg_kg_min_to_ml_hr(dose_mcg_kg_min: Float, weight_kg: Float, concentration_mcg_ml: Float) -> Float {
    return (dose_mcg_kg_min * weight_kg * 60.0) / concentration_mcg_ml
}

// Convert mg/hr dose to mL/hr infusion rate.
// mL/hr = dose_mg_hr / concentration_mg_per_mL
// Used for: morphine, fentanyl, midazolam, propofol, amiodarone.
fn mg_hr_to_ml_hr(dose_mg_hr: Float, concentration_mg_ml: Float) -> Float {
    return dose_mg_hr / concentration_mg_ml
}

// Convert units/hr dose to mL/hr infusion rate.
// mL/hr = dose_units_hr / concentration_units_per_mL
// Used for: heparin, insulin, oxytocin.
fn units_hr_to_ml_hr(dose_units_hr: Float, concentration_units_ml: Float) -> Float {
    return dose_units_hr / concentration_units_ml
}

// Back-calculate dose from infusion rate.
// dose_mcg_kg_min = (rate_mL_hr * concentration_mcg_mL) / (weight_kg * 60)
fn ml_hr_to_mcg_kg_min(rate_ml_hr: Float, weight_kg: Float, concentration_mcg_ml: Float) -> Float {
    return (rate_ml_hr * concentration_mcg_ml) / (weight_kg * 60.0)
}

// Total infusion time in hours from rate and volume.
fn infusion_time_hours(total_volume_ml: Float, rate_ml_hr: Float) -> Float {
    return total_volume_ml / rate_ml_hr
}


// =========================================================
// ELECTROLYTE CORRECTIONS
// =========================================================
//
// Lab values must be corrected for confounding factors before
// clinical decisions. Hyperglycemia dilutes sodium. Hypo-
// albuminemia falsely lowers total calcium. Low albumin also
// affects phenytoin binding. These corrections prevent both
// unnecessary treatment and missed diagnoses.
//
// DiPiro 12th ed, Ch 52: Disorders of Sodium, Water, Calcium,
// and Phosphorus Homeostasis.

// Corrected sodium for hyperglycemia.
// Corrected Na = measured Na + 1.6 * ((glucose - 100) / 100)
// For every 100 mg/dL glucose above 100, Na drops ~1.6 mEq/L.
// Some sources use 2.4 mEq/L correction for glucose > 400.
// Katz MA. N Engl J Med 289:843, 1973.
fn corrected_sodium(measured_na: Float, glucose: Float) -> Float {
    return measured_na + 1.6 * (glucose - 100.0) / 100.0
}

// Corrected calcium for hypoalbuminemia.
// Corrected Ca = measured Ca + 0.8 * (4.0 - albumin)
// For every 1 g/dL albumin below 4.0, total calcium increases
// by 0.8 mg/dL. Does not apply to ionized calcium.
// Payne RB et al. Br Med J 4:643, 1973.
fn corrected_calcium(measured_ca: Float, albumin: Float) -> Float {
    return measured_ca + 0.8 * (4.0 - albumin)
}

// Corrected phenytoin for hypoalbuminemia.
// Corrected = measured / (0.2 * albumin + 0.1)
// Phenytoin is 90% protein-bound. Low albumin increases free
// fraction, making total level appear subtherapeutic when free
// level may be adequate. Always check free phenytoin if available.
// Winter-Tozer equation. Winter 6th ed, Ch 10, pp 303-306.
fn corrected_phenytoin(measured_level: Float, albumin: Float) -> Float {
    return measured_level / (0.2 * albumin + 0.1)
}

// Corrected phenytoin for hypoalbuminemia AND renal failure.
// In renal failure (CrCl < 10-15 mL/min), binding affinity changes.
// Corrected = measured / (0.1 * albumin + 0.1)
// Winter 6th ed, Ch 10, pp 306-307.
fn corrected_phenytoin_renal(measured_level: Float, albumin: Float) -> Float {
    return measured_level / (0.1 * albumin + 0.1)
}

// Anion gap.
// AG = Na - (Cl + HCO3)
// Normal: 8-12 mEq/L (some labs 10-14 depending on analyzer).
// Elevated AG: metabolic acidosis from unmeasured anions
// (MUDPILES: methanol, uremia, DKA, propylene glycol,
// isoniazid/iron, lactic acidosis, ethylene glycol, salicylates).
fn anion_gap(na: Float, cl: Float, hco3: Float) -> Float {
    return na - (cl + hco3)
}

// Albumin-corrected anion gap.
// For every 1 g/dL albumin below 4.0, AG decreases by ~2.5.
// Corrected AG = AG + 2.5 * (4.0 - albumin)
// Figge J et al. Crit Care Med 26:1807, 1998.
fn corrected_anion_gap(ag: Float, albumin: Float) -> Float {
    return ag + 2.5 * (4.0 - albumin)
}

// Delta-delta ratio for mixed acid-base disorders.
// Delta AG = AG - 12 (normal AG)
// Delta HCO3 = 24 - measured HCO3
// Ratio = delta_AG / delta_HCO3
// Ratio < 1: concurrent non-anion-gap metabolic acidosis
// Ratio 1-2: pure anion-gap metabolic acidosis
// Ratio > 2: concurrent metabolic alkalosis
// Returns the ratio value.
fn delta_delta(ag: Float, hco3: Float) -> Float {
    let delta_ag = ag - 12.0
    let delta_hco3 = 24.0 - hco3
    if delta_hco3 == 0.0 { return 0.0 }
    return delta_ag / delta_hco3
}

// Delta-delta interpretation string.
fn delta_delta_interpret(ratio: Float) -> String {
    if ratio < 1.0 { return "Ratio < 1: Concurrent non-anion-gap metabolic acidosis (e.g., RTA, diarrhea)." }
    if ratio <= 2.0 { return "Ratio 1-2: Pure anion-gap metabolic acidosis." }
    return "Ratio > 2: Concurrent metabolic alkalosis (e.g., vomiting, diuretics) or pre-existing compensated respiratory acidosis."
}

// Calculated serum osmolality.
// Osm_calc = 2 * Na + glucose/18 + BUN/2.8
// Normal: 275-295 mOsm/kg.
fn calculated_osmolality(na: Float, glucose: Float, bun: Float) -> Float {
    return 2.0 * na + glucose / 18.0 + bun / 2.8
}

// Osmolal gap for toxic alcohol screening.
// OG = measured_osm - calculated_osm
// Normal: -10 to +10 mOsm/kg.
// Elevated OG (> 10): suspect methanol, ethylene glycol, ethanol,
// isopropanol, propylene glycol, mannitol.
fn osmolal_gap(measured_osm: Float, calculated_osm: Float) -> Float {
    return measured_osm - calculated_osm
}

// Osmolal gap including ethanol correction.
// If ethanol is measured (mg/dL), add ethanol/4.6 to calculated osm.
fn calculated_osmolality_with_ethanol(na: Float, glucose: Float, bun: Float, ethanol_mg_dl: Float) -> Float {
    return 2.0 * na + glucose / 18.0 + bun / 2.8 + ethanol_mg_dl / 4.6
}


// =========================================================
// WEIGHT-BASED DOSING
// =========================================================
//
// Many drugs are dosed on a per-kilogram or per-m^2 basis.
// These utility functions convert between total dose and
// weight-normalized dose for prescribing and verification.

// Calculate mg/kg from total dose and weight.
fn mg_per_kg(dose_mg: Float, weight_kg: Float) -> Float {
    return dose_mg / weight_kg
}

// Calculate total mg dose from mg/kg dosing.
fn dose_from_mg_kg(mg_kg: Float, weight_kg: Float) -> Float {
    return mg_kg * weight_kg
}

// Calculate mg/m^2 from total dose and BSA.
fn mg_per_m2(dose_mg: Float, bsa: Float) -> Float {
    return dose_mg / bsa
}

// Calculate total mg dose from mg/m^2 dosing.
fn dose_from_mg_m2(mg_m2: Float, bsa: Float) -> Float {
    return mg_m2 * bsa
}

// Dose per dose (given doses per day and daily total).
fn dose_per_administration(daily_total_mg: Float, doses_per_day: Float) -> Float {
    return daily_total_mg / doses_per_day
}

// Maximum dose cap (returns the lesser of calculated dose and max).
fn dose_with_cap(calculated_dose: Float, max_dose: Float) -> Float {
    if calculated_dose > max_dose { return max_dose }
    return calculated_dose
}

// Round dose to nearest practical increment (e.g., nearest 25 mg).
// Uses integer arithmetic: round = floor((dose + increment/2) / increment) * increment
fn round_dose(dose: Float, increment: Float) -> Float {
    let half_inc = increment / 2.0
    let shifted = dose + half_inc
    // Integer truncation via subtraction of remainder
    let quotient = shifted / increment
    // Floor by subtracting fractional part
    let floored = quotient - (quotient - abs(quotient))
    // Since Scyla has no floor(), we approximate:
    // For positive numbers, truncation works
    let result = floored * increment
    return result
}


// =========================================================
// CLINICAL REFERENCE VALUES
// =========================================================
//
// Normal ranges for lab values used in dosing calculations.
// All values from DiPiro 12th ed, Appendix: Laboratory Values.

fn normal_scr_male() -> String {
    return "Serum creatinine (male): 0.7-1.3 mg/dL"
}

fn normal_scr_female() -> String {
    return "Serum creatinine (female): 0.6-1.1 mg/dL"
}

fn normal_bun() -> String {
    return "BUN: 7-20 mg/dL"
}

fn normal_sodium() -> String {
    return "Sodium: 136-145 mEq/L"
}

fn normal_potassium() -> String {
    return "Potassium: 3.5-5.0 mEq/L"
}

fn normal_chloride() -> String {
    return "Chloride: 98-106 mEq/L"
}

fn normal_bicarbonate() -> String {
    return "Bicarbonate: 22-29 mEq/L"
}

fn normal_calcium() -> String {
    return "Calcium (total): 8.5-10.5 mg/dL. Ionized: 4.5-5.6 mg/dL (1.12-1.40 mmol/L)"
}

fn normal_albumin() -> String {
    return "Albumin: 3.5-5.0 g/dL"
}

fn normal_bilirubin() -> String {
    return "Total bilirubin: 0.1-1.2 mg/dL. Direct: 0-0.3 mg/dL"
}

fn normal_inr() -> String {
    return "INR: 0.9-1.1 (normal). Therapeutic warfarin: 2.0-3.0"
}

fn normal_glucose() -> String {
    return "Fasting glucose: 70-100 mg/dL. Random: < 140 mg/dL"
}


// =========================================================
// HEPARIN DOSING
// =========================================================
//
// Unfractionated heparin (UFH) dosing per weight-based
// nomogram. Standard for DVT/PE, ACS, and cardiac surgery.
// DiPiro 12th ed, Ch 19: Venous Thromboembolism.

struct HeparinProtocol {
    bolus_units: Float,
    infusion_units_hr: Float,
    notes: String,
}

// Weight-based heparin protocol for DVT/PE.
// Bolus: 80 units/kg (max 10000 units)
// Infusion: 18 units/kg/hr (max 2000 units/hr initially)
// Target aPTT: 1.5-2.5x control (or anti-Xa 0.3-0.7 IU/mL)
fn heparin_dvt_pe(weight_kg: Float) -> HeparinProtocol {
    let bolus = 80.0 * weight_kg
    if bolus > 10000.0 { bolus = 10000.0 }
    let infusion = 18.0 * weight_kg
    if infusion > 2000.0 { infusion = 2000.0 }
    return HeparinProtocol { bolus_units: bolus, infusion_units_hr: infusion, notes: "DVT/PE protocol. Target aPTT 1.5-2.5x control. Check aPTT 6h after initiation and after each dose change." }
}

// Weight-based heparin protocol for ACS (lower intensity).
// Bolus: 60 units/kg (max 4000 units)
// Infusion: 12 units/kg/hr (max 1000 units/hr)
// Target aPTT: 1.5-2.0x control
fn heparin_acs(weight_kg: Float) -> HeparinProtocol {
    let bolus = 60.0 * weight_kg
    if bolus > 4000.0 { bolus = 4000.0 }
    let infusion = 12.0 * weight_kg
    if infusion > 1000.0 { infusion = 1000.0 }
    return HeparinProtocol { bolus_units: bolus, infusion_units_hr: infusion, notes: "ACS protocol. Target aPTT 1.5-2.0x control. Check aPTT 6h after initiation. Monitor platelets for HIT." }
}


// =========================================================
// INSULIN DOSING
// =========================================================
//
// Insulin dosing for DKA and hyperglycemia management.
// DiPiro 12th ed, Ch 83: Diabetes Mellitus.

// Insulin drip for DKA/HHS.
// Standard: 0.1 units/kg/hr continuous infusion.
// Alternative: 0.14 units/kg/hr (no bolus).
// Target glucose reduction: 50-75 mg/dL per hour.
fn insulin_drip_dka(weight_kg: Float) -> Float {
    return 0.1 * weight_kg
}

// Correction dose sliding scale estimate.
// Sensitivity factor (1800 rule for rapid insulin):
// ISF = 1800 / total_daily_dose
// Correction = (current_glucose - target_glucose) / ISF
fn insulin_correction(current_glucose: Float, target_glucose: Float, total_daily_dose: Float) -> Float {
    let isf = 1800.0 / total_daily_dose
    let correction = (current_glucose - target_glucose) / isf
    if correction < 0.0 { return 0.0 }
    return correction
}

// Total daily insulin dose estimate for Type 2 DM initiation.
// Starting dose: 0.1-0.2 units/kg/day (basal only).
// Intensification: 0.3-0.5 units/kg/day (basal-bolus).
fn insulin_starting_dose(weight_kg: Float) -> Float {
    return 0.2 * weight_kg
}


// =========================================================
// WARFARIN DOSING
// =========================================================
//
// Warfarin has a narrow therapeutic index, significant genetic
// variability (CYP2C9, VKORC1), and numerous drug interactions.
// DiPiro 12th ed, Ch 19: Venous Thromboembolism.

// Estimated weekly warfarin dose from daily maintenance.
fn warfarin_weekly_dose(daily_mg: Float) -> Float {
    return daily_mg * 7.0
}

// INR interpretation for warfarin management.
fn inr_interpretation(inr: Float, target_low: Float, target_high: Float) -> String {
    if inr < target_low { return "INR below target. Consider dose increase 5-15%. Recheck in 1-2 weeks." }
    if inr <= target_high { return "INR within therapeutic range. Continue current dose." }
    if inr <= 5.0 { return "INR mildly supratherapeutic. Hold 1-2 doses. Reduce weekly dose 5-15%. Recheck in 1 week." }
    if inr <= 9.0 { return "INR significantly elevated. Hold warfarin. Consider vitamin K 2.5 mg PO. Recheck in 24-48 hours." }
    return "INR dangerously elevated (> 9). Hold warfarin. Vitamin K 5-10 mg PO. If bleeding: IV vitamin K + 4-factor PCC or FFP. Emergency."
}


// =========================================================
// DIGOXIN DOSING
// =========================================================
//
// Digoxin has a narrow therapeutic index (0.5-2.0 ng/mL)
// and primarily renal elimination. Dose adjustment for renal
// function is essential.
// Winter 6th ed, Ch 7: Digoxin, pp 213-260.

// Digoxin loading dose (total digitalizing dose).
// TDD (IV) = 8-12 mcg/kg lean body weight
// TDD (PO) = 10-15 mcg/kg lean body weight (F = 0.7-0.8)
// Give in divided doses: 50% initially, then 25% q6-8h x 2
fn digoxin_loading_iv(lean_weight_kg: Float) -> Float {
    return 10.0 * lean_weight_kg
}

fn digoxin_loading_po(lean_weight_kg: Float) -> Float {
    return 12.5 * lean_weight_kg
}

// Digoxin maintenance dose adjustment for renal function.
// Maintenance = loading_dose * percent_daily_loss / 100
// Percent daily loss = 14 + CrCl/5
// Winter 6th ed, Ch 7, pp 230-233.
fn digoxin_daily_loss_pct(crcl: Float) -> Float {
    return 14.0 + crcl / 5.0
}

fn digoxin_maintenance(loading_dose_mcg: Float, crcl: Float) -> Float {
    let daily_loss = digoxin_daily_loss_pct(crcl)
    return loading_dose_mcg * daily_loss / 100.0
}


// =========================================================
// PHENYTOIN DOSING (Michaelis-Menten Kinetics)
// =========================================================
//
// Phenytoin follows nonlinear (saturable) pharmacokinetics.
// Michaelis-Menten: rate = Vmax * Css / (Km + Css)
// Small dose changes produce disproportionate concentration
// changes near saturation. Monitor free levels.
// Winter 6th ed, Ch 10: Phenytoin, pp 293-332.

// Phenytoin daily dose at steady state (Michaelis-Menten).
// Daily dose = S * F * (Vmax * Css) / (Km + Css)
// S = salt factor (0.92 for phenytoin sodium)
// F = bioavailability (1.0 for IV, ~1.0 for extended-release PO)
// Vmax typical: 7 mg/kg/day, Km typical: 4 mcg/mL
fn phenytoin_daily_dose(vmax: Float, km: Float, target_css: Float, salt_factor: Float, bioavail: Float) -> Float {
    return salt_factor * bioavail * (vmax * target_css) / (km + target_css)
}

// Estimate Vmax and Km from two steady-state levels at different doses.
// This is the orbit plot method from Winter.
// Given: dose1 -> css1, dose2 -> css2
// Km = (dose1 * css2 - dose2 * css1) / (dose2 - dose1)
// Vmax = dose1 * (Km + css1) / css1
fn phenytoin_km_from_two_levels(dose1: Float, css1: Float, dose2: Float, css2: Float) -> Float {
    return (dose1 * css2 - dose2 * css1) / (dose2 - dose1)
}

fn phenytoin_vmax_from_two_levels(dose1: Float, css1: Float, km: Float) -> Float {
    return dose1 * (km + css1) / css1
}


// =========================================================
// THEOPHYLLINE DOSING
// =========================================================
//
// Theophylline has a narrow therapeutic range (5-15 mcg/mL)
// and first-order kinetics at usual doses. Clearance is highly
// variable: affected by age, smoking, CHF, liver disease,
// and CYP1A2 inhibitors (ciprofloxacin, erythromycin).
// Winter 6th ed, Ch 11: Theophylline, pp 333-374.

// Theophylline loading dose.
// LD (mg) = (target_conc - current_conc) * Vd
// Vd ~ 0.5 L/kg, salt factor for aminophylline = 0.8
fn theophylline_loading(target_conc: Float, current_conc: Float, weight_kg: Float) -> Float {
    let vd = 0.5 * weight_kg
    return (target_conc - current_conc) * vd
}

// Theophylline maintenance infusion rate.
// Rate (mg/hr) = CL (L/hr) * target_Css (mg/L)
// CL varies: nonsmoker 0.04 L/hr/kg, smoker 0.065, CHF 0.02
fn theophylline_infusion_rate(clearance_l_hr: Float, target_css: Float) -> Float {
    return clearance_l_hr * target_css
}

// Theophylline clearance estimates (L/hr/kg body weight).
fn theophylline_cl_nonsmoker() -> Float { return 0.04 }
fn theophylline_cl_smoker() -> Float { return 0.065 }
fn theophylline_cl_chf() -> Float { return 0.02 }
fn theophylline_cl_cirrhosis() -> Float { return 0.02 }
fn theophylline_cl_child_1_to_9() -> Float { return 0.067 }


// =========================================================
// LITHIUM DOSING
// =========================================================
//
// Lithium has a narrow therapeutic index (0.6-1.2 mEq/L)
// and is entirely renally eliminated (no hepatic metabolism).
// Dose adjustments mirror CrCl changes. Dehydration, NSAIDs,
// ACE inhibitors, and thiazides increase lithium levels.
// Winter 6th ed, Ch 8: Lithium, pp 261-292.

// Lithium dose estimation using population clearance.
// CL_lithium ~ 0.25 * CrCl (mL/min) in L/hr
// Daily dose (mEq) = CL * target_Css * 24 / F
// F ~ 1.0 for lithium carbonate
fn lithium_daily_dose(crcl: Float, target_css: Float) -> Float {
    // CL (L/hr) = 0.25 * CrCl (mL/min) / 1000 * 60 = CrCl * 0.015
    let cl_l_hr = crcl * 0.015
    // Daily dose in mEq = CL * target * 24
    return cl_l_hr * target_css * 24.0
}

// Convert lithium mEq to mg lithium carbonate.
// Lithium carbonate MW = 73.89 g/mol, each 300 mg capsule = 8.12 mEq
fn lithium_meq_to_mg_carbonate(meq: Float) -> Float {
    return meq * 300.0 / 8.12
}


// =========================================================
// ENOXAPARIN (LMWH) DOSING
// =========================================================
//
// Low molecular weight heparin dosing is weight-based with
// renal adjustment for CrCl < 30 mL/min.
// DiPiro 12th ed, Ch 19: Venous Thromboembolism.

// Enoxaparin treatment dose for DVT/PE.
// Treatment: mg/kg SC q12h, alternative 1.5 mg/kg SC q24h
// For CrCl < 30: 1 mg/kg SC q24h
fn enoxaparin_treatment(weight_kg: Float, crcl: Float) -> Float {
    if crcl < 30.0 { return weight_kg }
    return weight_kg
}

fn enoxaparin_treatment_interval(crcl: Float) -> Float {
    if crcl < 30.0 { return 24.0 }
    return 12.0
}

// Enoxaparin prophylaxis dose.
// Standard: 40 mg SC daily. Renal: 30 mg SC daily if CrCl < 30.
fn enoxaparin_prophylaxis(crcl: Float) -> Float {
    if crcl < 30.0 { return 30.0 }
    return 40.0
}


// =========================================================
// ANTI-FACTOR XA MONITORING
// =========================================================
//
// Anti-Xa levels for LMWH and fondaparinux monitoring.
// Peak (4h post-dose): treatment 0.5-1.0 IU/mL (q12h dosing)
// or 1.0-2.0 IU/mL (q24h dosing).

fn anti_xa_therapeutic_range_bid() -> String {
    return "Anti-Xa peak (4h post-dose, BID dosing): 0.5-1.0 IU/mL"
}

fn anti_xa_therapeutic_range_qd() -> String {
    return "Anti-Xa peak (4h post-dose, daily dosing): 1.0-2.0 IU/mL"
}


// =========================================================
// OBESITY DOSING CONSIDERATIONS
// =========================================================
//
// Obesity alters drug distribution (increased Vd for lipophilic
// drugs), protein binding (increased AAG, normal albumin),
// and clearance (increased renal and hepatic blood flow).
// DiPiro 12th ed, Ch 108.

// Determine which weight to use for drug dosing in obesity.
fn obesity_dosing_weight(drug_class: String, ibw_kg: Float, actual_kg: Float) -> Float {
    // Aminoglycosides: use ABW (IBW + 0.4 * excess)
    if drug_class == "AMINOGLYCOSIDE" { return ibw_kg + 0.4 * (actual_kg - ibw_kg) }
    // Vancomycin: use ACTUAL body weight (larger Vd in obesity)
    if drug_class == "VANCOMYCIN" { return actual_kg }
    // Enoxaparin: use ACTUAL body weight (max 150 kg per some protocols)
    if drug_class == "ENOXAPARIN" {
        if actual_kg > 150.0 { return 150.0 }
        return actual_kg
    }
    // Heparin: use ACTUAL body weight (with dose caps)
    if drug_class == "HEPARIN" { return actual_kg }
    // Fluoroquinolones: use ACTUAL body weight
    if drug_class == "FLUOROQUINOLONE" { return actual_kg }
    // Digoxin: use IBW (distributes to lean tissue)
    if drug_class == "DIGOXIN" { return ibw_kg }
    // Phenytoin: use ABW
    if drug_class == "PHENYTOIN" { return ibw_kg + 0.4 * (actual_kg - ibw_kg) }
    // Default: use IBW for safety
    return ibw_kg
}


// =========================================================
// UNIT CONVERSIONS FOR DOSING
// =========================================================

// Temperature: Celsius to Fahrenheit
fn celsius_to_fahrenheit(c: Float) -> Float {
    return c * 9.0 / 5.0 + 32.0
}

// Temperature: Fahrenheit to Celsius
fn fahrenheit_to_celsius(f: Float) -> Float {
    return (f - 32.0) * 5.0 / 9.0
}

// Weight: pounds to kilograms
fn lbs_to_kg(lbs: Float) -> Float {
    return lbs / 2.2046
}

// Weight: kilograms to pounds
fn kg_to_lbs(kg: Float) -> Float {
    return kg * 2.2046
}

// Height: inches to centimeters
fn inches_to_cm(inches: Float) -> Float {
    return inches * 2.54
}

// Height: centimeters to inches
fn cm_to_inches(cm: Float) -> Float {
    return cm / 2.54
}

// Height: feet and inches to total inches
fn feet_inches_to_inches(feet: Float, inches: Float) -> Float {
    return feet * 12.0 + inches
}

// Concentration: mg/dL to mmol/L (glucose)
fn glucose_mg_to_mmol(mg_dl: Float) -> Float {
    return mg_dl / 18.0
}

// Concentration: mmol/L to mg/dL (glucose)
fn glucose_mmol_to_mg(mmol_l: Float) -> Float {
    return mmol_l * 18.0
}

// Creatinine: mg/dL to umol/L
fn creatinine_mg_to_umol(mg_dl: Float) -> Float {
    return mg_dl * 88.4
}

// Creatinine: umol/L to mg/dL
fn creatinine_umol_to_mg(umol_l: Float) -> Float {
    return umol_l / 88.4
}

// Bilirubin: mg/dL to umol/L
fn bilirubin_mg_to_umol(mg_dl: Float) -> Float {
    return mg_dl * 17.1
}

// Calcium: mg/dL to mmol/L
fn calcium_mg_to_mmol(mg_dl: Float) -> Float {
    return mg_dl / 4.0
}

// Sodium/potassium: mEq/L = mmol/L (monovalent ions, same value)
fn meq_equals_mmol_monovalent() -> String {
    return "For monovalent ions (Na, K, Cl, HCO3): mEq/L = mmol/L"
}


// =========================================================
// COUNTS
// =========================================================

fn dosing_function_count() -> Int {
    return 104
}
