///|
/// Heat-transfer balances for preliminary equipment sizing.
pub struct WallLayer {
  name : String
  thickness_m : Float
  conductivity_w_mk : Float
}

///|
pub fn wall_layer(
  name : String,
  thickness_m : Float,
  conductivity_w_mk : Float,
) -> WallLayer {
  { name, thickness_m, conductivity_w_mk }
}

///|
pub fn WallLayer::resistance(self : WallLayer, area_m2 : Float) -> Float {
  if area_m2 == 0.0 || self.conductivity_w_mk == 0.0 {
    0.0
  } else {
    self.thickness_m / (self.conductivity_w_mk * area_m2)
  }
}

///|
pub fn conduction_resistance(
  thickness_m : Float,
  conductivity_w_mk : Float,
  area_m2 : Float,
) -> Float {
  if conductivity_w_mk == 0.0 || area_m2 == 0.0 {
    0.0
  } else {
    thickness_m / (conductivity_w_mk * area_m2)
  }
}

///|
pub fn convection_resistance(
  coefficient_w_m2k : Float,
  area_m2 : Float,
) -> Float {
  if coefficient_w_m2k == 0.0 || area_m2 == 0.0 {
    0.0
  } else {
    1.0 / (coefficient_w_m2k * area_m2)
  }
}

///|
pub fn radiation_coefficient(
  emissivity : Float,
  hot_k : Float,
  cold_k : Float,
) -> Float {
  5.670374419e-8 *
  emissivity *
  (hot_k + cold_k) *
  (hot_k * hot_k + cold_k * cold_k)
}

///|
pub fn heat_duty(
  mass_flow_kg_h : Float,
  heat_capacity_kj_kgk : Float,
  delta_temperature_k : Float,
) -> Float {
  mass_flow_kg_h * heat_capacity_kj_kgk * delta_temperature_k
}

///|
pub fn heat_duty_with_phase(
  mass_flow_kg_h : Float,
  heat_capacity_kj_kgk : Float,
  delta_temperature_k : Float,
  latent_kj_kg : Float,
) -> Float {
  heat_duty(mass_flow_kg_h, heat_capacity_kj_kgk, delta_temperature_k) +
  mass_flow_kg_h * latent_kj_kg
}

///|
pub fn lmtd(
  hot_in : Float,
  hot_out : Float,
  cold_in : Float,
  cold_out : Float,
) -> Float {
  let d1 = hot_in - cold_out
  let d2 = hot_out - cold_in
  if d1 == d2 {
    d1
  } else if d1 <= 0.0 || d2 <= 0.0 {
    0.0
  } else {
    2.0 * d1 * d2 / (d1 + d2)
  }
}

///|
pub fn exchanger_area(
  duty_kw : Float,
  coefficient_kw_m2k : Float,
  driving_force_k : Float,
  correction : Float,
) -> Float {
  if coefficient_kw_m2k == 0.0 || driving_force_k == 0.0 || correction == 0.0 {
    0.0
  } else {
    duty_kw / (coefficient_kw_m2k * driving_force_k * correction)
  }
}

///|
pub struct HeatExchangerCheck {
  area_m2 : Float
  required_m2 : Float
  duty_kw : Float
  pressure_drop_bar : Float
  max_pressure_drop_bar : Float
}

///|
pub fn heat_exchanger_check(
  area_m2 : Float,
  required_m2 : Float,
  duty_kw : Float,
  pressure_drop_bar : Float,
  max_pressure_drop_bar : Float,
) -> HeatExchangerCheck {
  { area_m2, required_m2, duty_kw, pressure_drop_bar, max_pressure_drop_bar }
}

///|
pub fn HeatExchangerCheck::area_margin(self : HeatExchangerCheck) -> Float {
  self.area_m2 - self.required_m2
}

///|
pub fn HeatExchangerCheck::acceptable(self : HeatExchangerCheck) -> Bool {
  self.area_margin() >= 0.0 &&
  self.pressure_drop_bar <= self.max_pressure_drop_bar
}

///|
pub struct InsulationDesign {
  thickness_m : Float
  conductivity_w_mk : Float
  area_m2 : Float
  ambient_c : Float
  process_c : Float
}

///|
pub fn insulation_design(
  thickness_m : Float,
  conductivity_w_mk : Float,
  area_m2 : Float,
  ambient_c : Float,
  process_c : Float,
) -> InsulationDesign {
  { thickness_m, conductivity_w_mk, area_m2, ambient_c, process_c }
}

///|
pub fn InsulationDesign::loss_w(self : InsulationDesign) -> Float {
  let r = conduction_resistance(
    self.thickness_m,
    self.conductivity_w_mk,
    self.area_m2,
  )
  if r == 0.0 {
    0.0
  } else {
    (self.process_c - self.ambient_c) / r
  }
}

///|
pub fn InsulationDesign::surface_load(self : InsulationDesign) -> Float {
  if self.area_m2 == 0.0 {
    0.0
  } else {
    self.loss_w() / self.area_m2
  }
}

///|
pub fn InsulationDesign::acceptable(
  self : InsulationDesign,
  limit_w_m2 : Float,
) -> Bool {
  self.surface_load() <= limit_w_m2
}

///|
pub fn heat_balance(
  inlet_kw : Float,
  reaction_kw : Float,
  utility_kw : Float,
  outlet_kw : Float,
) -> Float {
  inlet_kw + reaction_kw + utility_kw - outlet_kw
}

///|
pub fn heat_balance_gate(error_kw : Float, tolerance_kw : Float) -> Bool {
  error_kw.abs() <= tolerance_kw
}

///|
pub fn heat_transfer_table(checks : Array[HeatExchangerCheck]) -> ReportTable {
  let rows : Array[Array[String]] = []
  for c in checks {
    rows.push([
      "\{c.area_m2}",
      "\{c.required_m2}",
      "\{c.duty_kw}",
      "\{c.pressure_drop_bar}",
      "\{c.acceptable()}",
    ])
  }
  table(["area", "required", "duty", "pressure drop", "acceptable"], rows)
}