///|
pub(all) enum Spo2Scale {
  Scale1
  Scale2
} derive(Debug, Eq)

///|
pub(all) enum Consciousness {
  Alert
  NewConfusion
  Voice
  Pain
  Unresponsive
} derive(Debug, Eq)

///|
pub(all) struct News2Input {
  respiratory_rate : Int
  oxygen_saturation : Int
  spo2_scale : Spo2Scale
  supplemental_oxygen : Bool
  temperature_tenths_c : Int
  systolic_bp : Int
  heart_rate : Int
  consciousness : Consciousness
} derive(Debug, Eq)

///|
pub fn validate_news2(input : News2Input) -> ValidationError? {
  match validate_range("respiratory_rate", input.respiratory_rate, 0, 80) {
    Some(err) => Some(err)
    None =>
      match
        validate_range("oxygen_saturation", input.oxygen_saturation, 0, 100) {
        Some(err) => Some(err)
        None =>
          match
            validate_range(
              "temperature_tenths_c",
              input.temperature_tenths_c,
              250,
              450,
            ) {
            Some(err) => Some(err)
            None =>
              match validate_range("systolic_bp", input.systolic_bp, 40, 300) {
                Some(err) => Some(err)
                None => validate_range("heart_rate", input.heart_rate, 0, 250)
              }
          }
      }
  }
}

///|
pub fn news2_respiratory_rate_points(rate : Int) -> Int {
  if rate <= 8 {
    3
  } else if rate <= 11 {
    1
  } else if rate <= 20 {
    0
  } else if rate <= 24 {
    2
  } else {
    3
  }
}

///|
pub fn news2_oxygen_saturation_points(
  saturation : Int,
  scale : Spo2Scale,
  supplemental_oxygen : Bool,
) -> Int {
  match scale {
    Scale1 =>
      if saturation <= 91 {
        3
      } else if saturation <= 93 {
        2
      } else if saturation <= 95 {
        1
      } else {
        0
      }
    Scale2 =>
      if saturation <= 83 {
        3
      } else if saturation <= 85 {
        2
      } else if saturation <= 87 {
        1
      } else if saturation <= 92 {
        0
      } else if !supplemental_oxygen {
        0
      } else if saturation <= 94 {
        1
      } else if saturation <= 96 {
        2
      } else {
        3
      }
  }
}

///|
pub fn news2_supplemental_oxygen_points(supplemental_oxygen : Bool) -> Int {
  if supplemental_oxygen {
    2
  } else {
    0
  }
}

///|
pub fn news2_temperature_points(temperature_tenths_c : Int) -> Int {
  if temperature_tenths_c <= 350 {
    3
  } else if temperature_tenths_c <= 360 {
    1
  } else if temperature_tenths_c <= 380 {
    0
  } else if temperature_tenths_c <= 390 {
    1
  } else {
    2
  }
}

///|
pub fn news2_systolic_bp_points(systolic_bp : Int) -> Int {
  if systolic_bp <= 90 {
    3
  } else if systolic_bp <= 100 {
    2
  } else if systolic_bp <= 110 {
    1
  } else if systolic_bp <= 219 {
    0
  } else {
    3
  }
}

///|
pub fn news2_heart_rate_points(heart_rate : Int) -> Int {
  if heart_rate <= 40 {
    3
  } else if heart_rate <= 50 {
    1
  } else if heart_rate <= 90 {
    0
  } else if heart_rate <= 110 {
    1
  } else if heart_rate <= 130 {
    2
  } else {
    3
  }
}

///|
pub fn news2_consciousness_points(consciousness : Consciousness) -> Int {
  match consciousness {
    Alert => 0
    NewConfusion | Voice | Pain | Unresponsive => 3
  }
}

///|
pub fn news2_severity(score : Int, any_single_three : Bool) -> Severity {
  if score >= 7 {
    Critical
  } else if score >= 5 || any_single_three {
    High
  } else if score >= 1 {
    Medium
  } else {
    Low
  }
}

///|
fn news2_interpretation(score : Int, any_single_three : Bool) -> String {
  if score >= 7 {
    "High aggregate NEWS2 score: urgent clinical review is commonly expected in local protocols."
  } else if score >= 5 {
    "Medium aggregate NEWS2 score: local escalation protocol should be reviewed."
  } else if any_single_three {
    "Low aggregate score with one severe parameter: local escalation protocol should still be reviewed."
  } else if score >= 1 {
    "Low aggregate score: continue observation according to local policy."
  } else {
    "All measured NEWS2 parameters scored zero."
  }
}

///|
pub fn score_news2(input : News2Input) -> ScoreReport {
  let rr = news2_respiratory_rate_points(input.respiratory_rate)
  let spo2 = news2_oxygen_saturation_points(
    input.oxygen_saturation,
    input.spo2_scale,
    input.supplemental_oxygen,
  )
  let oxygen = news2_supplemental_oxygen_points(input.supplemental_oxygen)
  let temp = news2_temperature_points(input.temperature_tenths_c)
  let bp = news2_systolic_bp_points(input.systolic_bp)
  let pulse = news2_heart_rate_points(input.heart_rate)
  let neuro = news2_consciousness_points(input.consciousness)
  let score = rr + spo2 + oxygen + temp + bp + pulse + neuro
  let any_single_three = rr == 3 ||
    spo2 == 3 ||
    temp == 3 ||
    bp == 3 ||
    pulse == 3 ||
    neuro == 3
  report(
    "NEWS2",
    score,
    news2_severity(score, any_single_three),
    news2_interpretation(score, any_single_three),
    [
      explanation("Respiratory rate", rr, "NEWS2 respiratory-rate band"),
      explanation("Oxygen saturation", spo2, "NEWS2 SpO2 scale band"),
      explanation("Supplemental oxygen", oxygen, "NEWS2 oxygen therapy add-on"),
      explanation("Temperature", temp, "NEWS2 temperature band in Celsius"),
      explanation("Systolic blood pressure", bp, "NEWS2 systolic BP band"),
      explanation("Heart rate", pulse, "NEWS2 pulse band"),
      explanation("Consciousness", neuro, "NEWS2 ACVPU band"),
    ],
  )
}