///|
pub(all) enum SteamRegion {
  CompressedLiquid
  SaturatedLiquid
  TwoPhase
  SaturatedVapor
  SuperheatedVapor
  UnknownRegion
} derive(Debug, Eq)

///|
pub struct SteamState {
  temperature_c : Double
  pressure_kpa : Double
  enthalpy_kj_per_kg : Double
  entropy_kj_per_kg_k : Double
  specific_volume_m3_per_kg : Double
  quality : Double
  region : SteamRegion
} derive(Debug)

///|
pub struct SteamTableResult {
  value : Double
  lower_bound : Double
  upper_bound : Double
  clamped : Bool
  valid : Bool
} derive(Debug)

///|
fn saturation_temperature_from_pressure(pressure_kpa : Double) -> Double {
  if pressure_kpa <= 0.0 {
    0.0
  } else {
    let alpha = @math.ln(pressure_kpa / 0.61078)
    237.3 * alpha / (17.27 - alpha)
  }
}

///|
pub fn steam_saturation_temperature_c(pressure_kpa : Double) -> Double {
  saturation_temperature_from_pressure(pressure_kpa)
}

///|
fn steam_saturation_pressure_kpa(temperature_c : Double) -> Double {
  0.61078 * @math.exp(17.27 * temperature_c / (temperature_c + 237.3))
}

///|
pub fn steam_latent_heat_kj_per_kg(temperature_c : Double) -> Double {
  (2501.0 - 2.36 * temperature_c).max(0.0)
}

///|
pub fn saturated_liquid_enthalpy_kj_per_kg(temperature_c : Double) -> Double {
  4.18 * temperature_c
}

///|
pub fn saturated_vapor_enthalpy_kj_per_kg(temperature_c : Double) -> Double {
  saturated_liquid_enthalpy_kj_per_kg(temperature_c) +
  steam_latent_heat_kj_per_kg(temperature_c)
}

///|
pub fn saturated_liquid_entropy_kj_per_kg_k(temperature_c : Double) -> Double {
  let temperature_k = celsius_to_kelvin(temperature_c)
  if temperature_k <= 0.0 {
    0.0
  } else {
    4.18 * @math.ln(temperature_k / 273.15)
  }
}

///|
pub fn saturated_liquid_specific_volume_m3_per_kg(
  temperature_c : Double,
) -> Double {
  (0.001 + 0.0000003 * temperature_c).max(0.0005)
}

///|
pub fn saturated_vapor_specific_volume_m3_per_kg(
  temperature_c : Double,
  pressure_kpa : Double,
) -> Double {
  let pressure_pa = kpa_to_pa(pressure_kpa)
  let temperature_k = celsius_to_kelvin(temperature_c)
  if pressure_pa <= 0.0 || temperature_k <= 0.0 {
    0.0
  } else {
    0.4615 * temperature_k / pressure_pa * 1000.0
  }
}

///|
pub fn steam_state(temperature_c : Double, pressure_kpa : Double) -> SteamState {
  let saturation = steam_saturation_temperature_c(pressure_kpa)
  let liquid_h = saturated_liquid_enthalpy_kj_per_kg(saturation)
  let vapor_h = saturated_vapor_enthalpy_kj_per_kg(saturation)
  if pressure_kpa <= 0.0 || temperature_c < -50.0 {
    {
      temperature_c,
      pressure_kpa,
      enthalpy_kj_per_kg: 0.0,
      entropy_kj_per_kg_k: 0.0,
      specific_volume_m3_per_kg: 0.0,
      quality: 0.0,
      region: UnknownRegion,
    }
  } else if (temperature_c - saturation).abs() < 0.5 {
    let quality = 0.5
    {
      temperature_c,
      pressure_kpa,
      enthalpy_kj_per_kg: liquid_h + quality * (vapor_h - liquid_h),
      entropy_kj_per_kg_k: saturated_liquid_entropy_kj_per_kg_k(saturation) +
      quality,
      specific_volume_m3_per_kg: saturated_vapor_specific_volume_m3_per_kg(
        saturation, pressure_kpa,
      ) *
      quality,
      quality,
      region: TwoPhase,
    }
  } else if temperature_c < saturation {
    {
      temperature_c,
      pressure_kpa,
      enthalpy_kj_per_kg: saturated_liquid_enthalpy_kj_per_kg(temperature_c),
      entropy_kj_per_kg_k: saturated_liquid_entropy_kj_per_kg_k(temperature_c),
      specific_volume_m3_per_kg: saturated_liquid_specific_volume_m3_per_kg(
        temperature_c,
      ),
      quality: 0.0,
      region: CompressedLiquid,
    }
  } else {
    let superheat = temperature_c - saturation
    let enthalpy = vapor_h + 2.08 * superheat
    {
      temperature_c,
      pressure_kpa,
      enthalpy_kj_per_kg: enthalpy,
      entropy_kj_per_kg_k: saturated_liquid_entropy_kj_per_kg_k(saturation) +
      1.0 +
      2.08 *
      @math.ln(celsius_to_kelvin(temperature_c) / celsius_to_kelvin(saturation)),
      specific_volume_m3_per_kg: saturated_vapor_specific_volume_m3_per_kg(
        saturation, pressure_kpa,
      ) *
      celsius_to_kelvin(temperature_c) /
      celsius_to_kelvin(saturation),
      quality: 1.0,
      region: SuperheatedVapor,
    }
  }
}

///|
pub fn steam_state_from_quality(
  pressure_kpa : Double,
  quality : Double,
) -> SteamState {
  let temperature = steam_saturation_temperature_c(pressure_kpa)
  let x = quality.max(0.0).min(1.0)
  let liquid_h = saturated_liquid_enthalpy_kj_per_kg(temperature)
  let vapor_h = saturated_vapor_enthalpy_kj_per_kg(temperature)
  {
    temperature_c: temperature,
    pressure_kpa,
    enthalpy_kj_per_kg: liquid_h + x * (vapor_h - liquid_h),
    entropy_kj_per_kg_k: saturated_liquid_entropy_kj_per_kg_k(temperature) + x,
    specific_volume_m3_per_kg: saturated_liquid_specific_volume_m3_per_kg(
      temperature,
    ) +
    x * saturated_vapor_specific_volume_m3_per_kg(temperature, pressure_kpa),
    quality: x,
    region: if x == 0.0 {
      SaturatedLiquid
    } else if x == 1.0 {
      SaturatedVapor
    } else {
      TwoPhase
    },
  }
}

///|
pub fn steam_mass_flow_for_duty(
  duty_w : Double,
  pressure_kpa : Double,
  quality_in : Double,
  quality_out : Double,
) -> Double {
  let inlet = steam_state_from_quality(pressure_kpa, quality_in)
  let outlet = steam_state_from_quality(pressure_kpa, quality_out)
  let delta_h = (outlet.enthalpy_kj_per_kg - inlet.enthalpy_kj_per_kg) * 1000.0
  if delta_h.abs() < 1.0e-12 {
    0.0
  } else {
    duty_w / delta_h
  }
}

///|
pub fn interpolate_table(
  lower_x : Double,
  lower_y : Double,
  upper_x : Double,
  upper_y : Double,
  x : Double,
) -> SteamTableResult {
  if upper_x <= lower_x {
    {
      value: 0.0,
      lower_bound: lower_x,
      upper_bound: upper_x,
      clamped: false,
      valid: false,
    }
  } else {
    let clamped = x < lower_x || x > upper_x
    let bounded = x.max(lower_x).min(upper_x)
    let fraction = (bounded - lower_x) / (upper_x - lower_x)
    {
      value: lower_y + fraction * (upper_y - lower_y),
      lower_bound: lower_x,
      upper_bound: upper_x,
      clamped,
      valid: true,
    }
  }
}