///|
pub(all) struct NexusCspineInput {
  posterior_midline_tenderness : Bool
  focal_neurologic_deficit : Bool
  altered_alertness : Bool
  intoxication : Bool
  painful_distracting_injury : Bool
} derive(Debug, Eq)

///|
pub fn nexus_positive_count(input : NexusCspineInput) -> Int {
  let midline = if input.posterior_midline_tenderness { 1 } else { 0 }
  let neurologic = if input.focal_neurologic_deficit { 1 } else { 0 }
  let alertness = if input.altered_alertness { 1 } else { 0 }
  let intoxication = if input.intoxication { 1 } else { 0 }
  let distracting = if input.painful_distracting_injury { 1 } else { 0 }
  midline + neurologic + alertness + intoxication + distracting
}

///|
pub fn nexus_clears_cspine(input : NexusCspineInput) -> Bool {
  nexus_positive_count(input) == 0
}

///|
pub fn score_nexus_cspine(input : NexusCspineInput) -> ScoreReport {
  let score = nexus_positive_count(input)
  let severity = if score == 0 { Low } else { High }
  report(
    "NEXUS C-spine",
    score,
    severity,
    "NEXUS exposes cervical-spine imaging criteria for appropriately selected blunt-trauma patients.",
    [
      explanation(
        "Midline tenderness",
        if input.posterior_midline_tenderness {
          1
        } else {
          0
        },
        "Posterior midline tenderness",
      ),
      explanation(
        "Neurologic deficit",
        if input.focal_neurologic_deficit {
          1
        } else {
          0
        },
        "Focal neurologic deficit",
      ),
      explanation(
        "Alertness",
        if input.altered_alertness {
          1
        } else {
          0
        },
        "Altered level of alertness",
      ),
      explanation(
        "Intoxication",
        if input.intoxication {
          1
        } else {
          0
        },
        "Evidence of intoxication",
      ),
      explanation(
        "Distracting injury",
        if input.painful_distracting_injury {
          1
        } else {
          0
        },
        "Painful distracting injury",
      ),
    ],
  )
}

///|
pub(all) struct CanadianCspineInput {
  age : Int
  dangerous_mechanism : Bool
  paresthesias : Bool
  low_risk_simple_rear_end_collision : Bool
  low_risk_sitting_in_ed : Bool
  low_risk_ambulatory : Bool
  low_risk_delayed_neck_pain : Bool
  low_risk_no_midline_tenderness : Bool
  rotates_neck_45_degrees : Bool
} derive(Debug, Eq)

///|
pub fn validate_canadian_cspine(
  input : CanadianCspineInput,
) -> ValidationError? {
  validate_range("age", input.age, 0, 150)
}

///|
pub fn canadian_cspine_high_risk_count(input : CanadianCspineInput) -> Int {
  let age = if input.age >= 65 { 1 } else { 0 }
  let mechanism = if input.dangerous_mechanism { 1 } else { 0 }
  let paresthesia = if input.paresthesias { 1 } else { 0 }
  age + mechanism + paresthesia
}

///|
pub fn canadian_cspine_low_risk_count(input : CanadianCspineInput) -> Int {
  let rear = if input.low_risk_simple_rear_end_collision { 1 } else { 0 }
  let sitting = if input.low_risk_sitting_in_ed { 1 } else { 0 }
  let ambulatory = if input.low_risk_ambulatory { 1 } else { 0 }
  let delayed = if input.low_risk_delayed_neck_pain { 1 } else { 0 }
  let no_tenderness = if input.low_risk_no_midline_tenderness { 1 } else { 0 }
  rear + sitting + ambulatory + delayed + no_tenderness
}

///|
pub fn canadian_cspine_positive(input : CanadianCspineInput) -> Bool {
  canadian_cspine_high_risk_count(input) > 0 || !input.rotates_neck_45_degrees
}

///|
pub fn score_canadian_cspine(input : CanadianCspineInput) -> ScoreReport {
  let high = canadian_cspine_high_risk_count(input)
  let low = canadian_cspine_low_risk_count(input)
  let rotation = if input.rotates_neck_45_degrees { 0 } else { 1 }
  let score = high + rotation
  let severity = if canadian_cspine_positive(input) { High } else { Low }
  report(
    "Canadian C-spine",
    score,
    severity,
    "The Canadian C-spine rule separates high-risk features, low-risk assessment, and active neck rotation.",
    [
      explanation("High-risk criteria", high, "Age, mechanism, or paresthesia"),
      explanation("Low-risk criteria", low, "Low-risk clinical features"),
      explanation("Neck rotation", rotation, "Unable to rotate neck 45 degrees"),
    ],
  )
}

///|
pub(all) struct PercInput {
  age : Int
  heart_rate : Int
  oxygen_saturation : Int
  hemoptysis : Bool
  estrogen_use : Bool
  prior_vte : Bool
  recent_surgery_or_trauma : Bool
  unilateral_leg_swelling : Bool
} derive(Debug, Eq)

///|
pub fn validate_perc(input : PercInput) -> ValidationError? {
  match validate_range("age", input.age, 0, 150) {
    Some(err) => Some(err)
    None =>
      match validate_range("heart_rate", input.heart_rate, 0, 300) {
        Some(err) => Some(err)
        None =>
          validate_range("oxygen_saturation", input.oxygen_saturation, 0, 100)
      }
  }
}

///|
pub fn perc_positive_count(input : PercInput) -> Int {
  let age = if input.age >= 50 { 1 } else { 0 }
  let pulse = if input.heart_rate >= 100 { 1 } else { 0 }
  let oxygen = if input.oxygen_saturation < 95 { 1 } else { 0 }
  let hemoptysis = if input.hemoptysis { 1 } else { 0 }
  let estrogen = if input.estrogen_use { 1 } else { 0 }
  let prior = if input.prior_vte { 1 } else { 0 }
  let surgery = if input.recent_surgery_or_trauma { 1 } else { 0 }
  let swelling = if input.unilateral_leg_swelling { 1 } else { 0 }
  age + pulse + oxygen + hemoptysis + estrogen + prior + surgery + swelling
}

///|
pub fn perc_negative(input : PercInput) -> Bool {
  perc_positive_count(input) == 0
}

///|
pub fn score_perc(input : PercInput) -> ScoreReport {
  let score = perc_positive_count(input)
  report(
    "PERC",
    score,
    if score == 0 {
      Low
    } else {
      Medium
    },
    "PERC counts pulmonary-embolism exclusion criteria only after a clinician has established low pretest probability.",
    [
      explanation("Age", if input.age >= 50 { 1 } else { 0 }, "Age threshold"),
      explanation(
        "Pulse",
        if input.heart_rate >= 100 {
          1
        } else {
          0
        },
        "Heart rate threshold",
      ),
      explanation(
        "Oxygen",
        if input.oxygen_saturation < 95 {
          1
        } else {
          0
        },
        "Oxygen saturation threshold",
      ),
      explanation(
        "Hemoptysis",
        if input.hemoptysis {
          1
        } else {
          0
        },
        "Hemoptysis",
      ),
      explanation(
        "Estrogen",
        if input.estrogen_use {
          1
        } else {
          0
        },
        "Estrogen exposure",
      ),
      explanation(
        "Prior VTE",
        if input.prior_vte {
          1
        } else {
          0
        },
        "Previous venous thromboembolism",
      ),
      explanation(
        "Surgery or trauma",
        if input.recent_surgery_or_trauma {
          1
        } else {
          0
        },
        "Recent surgery or trauma",
      ),
      explanation(
        "Leg swelling",
        if input.unilateral_leg_swelling {
          1
        } else {
          0
        },
        "Unilateral leg swelling",
      ),
    ],
  )
}

///|
pub(all) struct OttawaAnkleInput {
  posterior_edge_lateral_malleolus_tender : Bool
  posterior_edge_medial_malleolus_tender : Bool
  base_of_fifth_metatarsal_tender : Bool
  navicular_tender : Bool
  unable_to_bear_four_steps : Bool
} derive(Debug, Eq)

///|
pub fn ottawa_ankle_positive(input : OttawaAnkleInput) -> Bool {
  input.posterior_edge_lateral_malleolus_tender ||
  input.posterior_edge_medial_malleolus_tender ||
  input.base_of_fifth_metatarsal_tender ||
  input.navicular_tender ||
  input.unable_to_bear_four_steps
}

///|
pub fn ottawa_ankle_criterion_count(input : OttawaAnkleInput) -> Int {
  let lateral = if input.posterior_edge_lateral_malleolus_tender {
    1
  } else {
    0
  }
  let medial = if input.posterior_edge_medial_malleolus_tender { 1 } else { 0 }
  let fifth = if input.base_of_fifth_metatarsal_tender { 1 } else { 0 }
  let navicular = if input.navicular_tender { 1 } else { 0 }
  let steps = if input.unable_to_bear_four_steps { 1 } else { 0 }
  lateral + medial + fifth + navicular + steps
}

///|
pub fn score_ottawa_ankle(input : OttawaAnkleInput) -> ScoreReport {
  let score = ottawa_ankle_criterion_count(input)
  report(
    "Ottawa Ankle",
    score,
    if ottawa_ankle_positive(input) {
      High
    } else {
      Low
    },
    "Ottawa ankle criteria identify when ankle or foot radiographs should be considered after acute injury.",
    [
      explanation(
        "Lateral malleolus",
        if input.posterior_edge_lateral_malleolus_tender {
          1
        } else {
          0
        },
        "Posterior edge tenderness",
      ),
      explanation(
        "Medial malleolus",
        if input.posterior_edge_medial_malleolus_tender {
          1
        } else {
          0
        },
        "Posterior edge tenderness",
      ),
      explanation(
        "Fifth metatarsal",
        if input.base_of_fifth_metatarsal_tender {
          1
        } else {
          0
        },
        "Base tenderness",
      ),
      explanation(
        "Navicular",
        if input.navicular_tender {
          1
        } else {
          0
        },
        "Navicular tenderness",
      ),
      explanation(
        "Weight bearing",
        if input.unable_to_bear_four_steps {
          1
        } else {
          0
        },
        "Unable to bear four steps",
      ),
    ],
  )
}

///|
pub(all) struct PecarnHeadInput {
  age_under_two : Bool
  altered_mentation : Bool
  palpable_skull_fracture : Bool
  nonfrontal_scalp_hematoma : Bool
  loss_of_consciousness_minutes : Int
  severe_mechanism : Bool
  not_behaving_normally : Bool
  signs_of_basilar_skull_fracture : Bool
  severe_headache : Bool
} derive(Debug, Eq)

///|
pub fn validate_pecarn_head(input : PecarnHeadInput) -> ValidationError? {
  validate_range(
    "loss_of_consciousness_minutes",
    input.loss_of_consciousness_minutes,
    0,
    1000,
  )
}

///|
pub fn pecarn_high_risk_count(input : PecarnHeadInput) -> Int {
  let mentation = if input.altered_mentation { 1 } else { 0 }
  let skull = if input.palpable_skull_fracture { 1 } else { 0 }
  let basilar = if input.signs_of_basilar_skull_fracture { 1 } else { 0 }
  mentation + skull + basilar
}

///|
pub fn pecarn_intermediate_count(input : PecarnHeadInput) -> Int {
  let scalp = if input.nonfrontal_scalp_hematoma { 1 } else { 0 }
  let loc = if input.loss_of_consciousness_minutes > 0 { 1 } else { 0 }
  let mechanism = if input.severe_mechanism { 1 } else { 0 }
  let behavior = if input.not_behaving_normally { 1 } else { 0 }
  let headache = if input.severe_headache { 1 } else { 0 }
  scalp + loc + mechanism + behavior + headache
}

///|
pub fn score_pecarn_head(input : PecarnHeadInput) -> ScoreReport {
  let high = pecarn_high_risk_count(input)
  let intermediate = pecarn_intermediate_count(input)
  let score = high * 2 + intermediate
  report(
    "PECARN Head Injury",
    score,
    if high > 0 {
      Critical
    } else if intermediate > 0 {
      Medium
    } else {
      Low
    },
    "PECARN exposes age-specific pediatric head-injury risk features and is not a substitute for clinical assessment.",
    [
      explanation("High-risk criteria", high, "High-risk feature count"),
      explanation(
        "Intermediate criteria", intermediate, "Intermediate-risk feature count",
      ),
      explanation(
        "Age group",
        if input.age_under_two {
          0
        } else {
          1
        },
        "Age-group branch selected",
      ),
    ],
  )
}