///|
pub fn reversible_first_order_rate(
  model : ReversibleFirstOrder,
  reactant_concentration : Double,
  product_concentration : Double,
) -> Double {
  let reactant = reactant_concentration.max(0.0)
  let product = product_concentration.max(0.0)
  let raw = model.forward_rate.max(0.0) * reactant -
    model.reverse_rate.max(0.0) * product
  if model.equilibrium_concentration > 0.0 &&
    product >= model.equilibrium_concentration &&
    raw > 0.0 {
    0.0
  } else {
    raw
  }
}

///|
pub fn series_first_order_batch(
  initial_concentration : Double,
  first_rate : Double,
  second_rate : Double,
  time : Double,
) -> SeriesReactionResult {
  let c0 = initial_concentration.max(0.0)
  let k1 = first_rate.max(0.0)
  let k2 = second_rate.max(0.0)
  let t = time.max(0.0)
  let ca = c0 * @math.exp(-k1 * t)
  let cb = if k1 == k2 {
    c0 * k1 * t * @math.exp(-k1 * t)
  } else {
    c0 * k1 / (k2 - k1) * (@math.exp(-k1 * t) - @math.exp(-k2 * t))
  }
  let intermediate = cb.max(0.0)
  let product = (c0 - ca - intermediate).max(0.0)
  let converted = intermediate + product
  {
    intermediate_concentration: intermediate,
    product_concentration: product,
    intermediate_selectivity: if converted <= 0.0 {
      0.0
    } else {
      (intermediate / converted).clamp(min=0.0, max=1.0)
    },
  }
}

///|
pub fn parallel_first_order_rates(
  concentration : Double,
  product_a_rate_constant : Double,
  product_b_rate_constant : Double,
) -> ParallelReactionResult {
  let c = concentration.max(0.0)
  let ka = product_a_rate_constant.max(0.0)
  let kb = product_b_rate_constant.max(0.0)
  let rate_a = ka * c
  let rate_b = kb * c
  let total = rate_a + rate_b
  {
    product_a_rate: rate_a,
    product_b_rate: rate_b,
    product_a_selectivity: if total <= 0.0 {
      0.0
    } else {
      rate_a / total
    },
    product_b_selectivity: if total <= 0.0 {
      0.0
    } else {
      rate_b / total
    },
  }
}