//! Physical types using the NewType pattern for compile-time unit safety.
//!
//! This module provides type-safe wrappers around `f64` for physical quantities,
//! preventing accidental mixing of units at compile time.
//!
//! All types store values in SI base units internally:
//! - Pressure: Pascals (Pa)
//! - Temperature: Kelvin (K)
//! - Enthalpy: Joules per kilogram (J/kg)
//! - MassFlow: Kilograms per second (kg/s)
//! - Power: Watts (W)
//! - Concentration: Dimensionless fraction [0.0, 1.0]
//! - VolumeFlow: Cubic meters per second (m³/s)
//! - RelativeHumidity: Dimensionless fraction [0.0, 1.0]
//! - VaporQuality: Dimensionless fraction [0.0, 1.0]
//! - Entropy: Joules per kilogram per Kelvin (J/(kg·K))
//! - ThermalConductance: Watts per Kelvin (W/K)
//!
//! # Type Safety
//!
//! These types cannot be mixed accidentally - the following will not compile:
//!
//! ```compile_fail
//! use entropyk_core::{Pressure, Temperature};
//!
//! let p = Pressure::from_bar(1.0);
//! let t = Temperature::from_celsius(25.0);
//! // This is a compile error - cannot add Pressure and Temperature!
//! let _invalid = p + t; // ERROR: mismatched types
//! ```

use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Add, Div, Mul, Sub};

/// Pressure in Pascals (Pa).
///
/// Internally stores the value in Pascals (SI base unit).
/// Provides conversions to/from common units like bar.
///
/// # Example
///
/// ```
/// use entropyk_core::Pressure;
///
/// let p = Pressure::from_bar(1.0);
/// assert_eq!(p.to_pascals(), 100_000.0);
/// assert_eq!(p.to_bar(), 1.0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Pressure(pub f64);

impl Pressure {
    /// Creates a Pressure from a value in Pascals.
    pub fn from_pascals(value: f64) -> Self {
        Pressure(value)
    }

    /// Creates a Pressure from a value in bar.
    pub fn from_bar(value: f64) -> Self {
        Pressure(value * 100_000.0)
    }

    /// Creates a Pressure from a value in PSI (pounds per square inch).
    pub fn from_psi(value: f64) -> Self {
        Pressure(value * 6894.75729)
    }

    /// Returns the pressure in Pascals.
    pub fn to_pascals(&self) -> f64 {
        self.0
    }

    /// Returns the pressure in bar.
    pub fn to_bar(&self) -> f64 {
        self.0 / 100_000.0
    }

    /// Returns the pressure in PSI (pounds per square inch).
    pub fn to_psi(&self) -> f64 {
        self.0 / 6894.75729
    }
}

impl fmt::Display for Pressure {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} Pa", self.0)
    }
}

impl From<f64> for Pressure {
    fn from(value: f64) -> Self {
        Pressure(value)
    }
}

impl Add<Pressure> for Pressure {
    type Output = Pressure;

    fn add(self, other: Pressure) -> Pressure {
        Pressure(self.0 + other.0)
    }
}

impl Sub<Pressure> for Pressure {
    type Output = Pressure;

    fn sub(self, other: Pressure) -> Pressure {
        Pressure(self.0 - other.0)
    }
}

impl Mul<f64> for Pressure {
    type Output = Pressure;

    fn mul(self, scalar: f64) -> Pressure {
        Pressure(self.0 * scalar)
    }
}

impl Mul<Pressure> for f64 {
    type Output = Pressure;

    fn mul(self, p: Pressure) -> Pressure {
        Pressure(self * p.0)
    }
}

impl Div<f64> for Pressure {
    type Output = Pressure;

    fn div(self, scalar: f64) -> Pressure {
        Pressure(self.0 / scalar)
    }
}

/// Temperature in Kelvin (K).
///
/// Internally stores the value in Kelvin (SI base unit).
/// Provides conversions to/from Celsius.
///
/// # Example
///
/// ```
/// use entropyk_core::Temperature;
///
/// let t = Temperature::from_celsius(0.0);
/// assert_eq!(t.to_kelvin(), 273.15);
/// assert_eq!(t.to_celsius(), 0.0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Temperature(pub f64);

impl Temperature {
    /// Creates a Temperature from a value in Kelvin.
    pub fn from_kelvin(value: f64) -> Self {
        Temperature(value)
    }

    /// Creates a Temperature from a value in Celsius.
    pub fn from_celsius(value: f64) -> Self {
        Temperature(value + 273.15)
    }

    /// Creates a Temperature from a value in Fahrenheit.
    pub fn from_fahrenheit(value: f64) -> Self {
        Temperature((value - 32.0) * 5.0 / 9.0 + 273.15)
    }

    /// Returns the temperature in Kelvin.
    pub fn to_kelvin(&self) -> f64 {
        self.0
    }

    /// Returns the temperature in Celsius.
    pub fn to_celsius(&self) -> f64 {
        self.0 - 273.15
    }

    /// Returns the temperature in Fahrenheit.
    pub fn to_fahrenheit(&self) -> f64 {
        (self.0 - 273.15) * 9.0 / 5.0 + 32.0
    }
}

impl fmt::Display for Temperature {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} K", self.0)
    }
}

impl From<f64> for Temperature {
    fn from(value: f64) -> Self {
        Temperature(value)
    }
}

impl Add<Temperature> for Temperature {
    type Output = Temperature;

    fn add(self, other: Temperature) -> Temperature {
        Temperature(self.0 + other.0)
    }
}

impl Sub<Temperature> for Temperature {
    type Output = Temperature;

    fn sub(self, other: Temperature) -> Temperature {
        Temperature(self.0 - other.0)
    }
}

impl Mul<f64> for Temperature {
    type Output = Temperature;

    fn mul(self, scalar: f64) -> Temperature {
        Temperature(self.0 * scalar)
    }
}

impl Mul<Temperature> for f64 {
    type Output = Temperature;

    fn mul(self, t: Temperature) -> Temperature {
        Temperature(self * t.0)
    }
}

impl Div<f64> for Temperature {
    type Output = Temperature;

    fn div(self, scalar: f64) -> Temperature {
        Temperature(self.0 / scalar)
    }
}

/// Specific enthalpy in Joules per kilogram (J/kg).
///
/// Internally stores the value in Joules per kilogram (SI base unit).
///
/// # Example
///
/// ```
/// use entropyk_core::Enthalpy;
///
/// let h = Enthalpy::from_joules_per_kg(1000.0);
/// assert_eq!(h.to_joules_per_kg(), 1000.0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Enthalpy(pub f64);

impl Enthalpy {
    /// Creates an Enthalpy from a value in Joules per kilogram.
    pub fn from_joules_per_kg(value: f64) -> Self {
        Enthalpy(value)
    }

    /// Creates an Enthalpy from a value in kilojoules per kilogram.
    pub fn from_kilojoules_per_kg(value: f64) -> Self {
        Enthalpy(value * 1_000.0)
    }

    /// Returns the enthalpy in Joules per kilogram.
    pub fn to_joules_per_kg(&self) -> f64 {
        self.0
    }

    /// Returns the enthalpy in kilojoules per kilogram.
    pub fn to_kilojoules_per_kg(&self) -> f64 {
        self.0 / 1_000.0
    }
}

impl fmt::Display for Enthalpy {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} J/kg", self.0)
    }
}

impl From<f64> for Enthalpy {
    fn from(value: f64) -> Self {
        Enthalpy(value)
    }
}

impl Add<Enthalpy> for Enthalpy {
    type Output = Enthalpy;

    fn add(self, other: Enthalpy) -> Enthalpy {
        Enthalpy(self.0 + other.0)
    }
}

impl Sub<Enthalpy> for Enthalpy {
    type Output = Enthalpy;

    fn sub(self, other: Enthalpy) -> Enthalpy {
        Enthalpy(self.0 - other.0)
    }
}

impl Mul<f64> for Enthalpy {
    type Output = Enthalpy;

    fn mul(self, scalar: f64) -> Enthalpy {
        Enthalpy(self.0 * scalar)
    }
}

impl Mul<Enthalpy> for f64 {
    type Output = Enthalpy;

    fn mul(self, h: Enthalpy) -> Enthalpy {
        Enthalpy(self * h.0)
    }
}

impl Div<f64> for Enthalpy {
    type Output = Enthalpy;

    fn div(self, scalar: f64) -> Enthalpy {
        Enthalpy(self.0 / scalar)
    }
}

/// Minimum mass flow used in denominators to avoid division by zero (zero-flow regularization).
///
/// When mass flow is zero or below this value, use [`MassFlow::regularized`] in any expression
/// that divides by mass flow (e.g. Q/ṁ, ΔP/ṁ²) or by quantities derived from it (e.g. Reynolds,
/// capacity rate C = ṁ·Cp). This prevents NaN/Inf while preserving solver convergence.
///
/// Value: 1e-12 kg/s (small enough to not affect physical results when ṁ >> ε).
pub const MIN_MASS_FLOW_REGULARIZATION_KG_S: f64 = 1e-12;

/// Mass flow rate in kilograms per second (kg/s).
///
/// Internally stores the value in kilograms per second (SI base unit).
///
/// # Zero-flow regularization
///
/// When dividing by mass flow (or using it in denominators), use [`MassFlow::regularized`] so that
/// zero-flow branches do not cause division by zero. See [`MIN_MASS_FLOW_REGULARIZATION_KG_S`].
///
/// # Example
///
/// ```
/// use entropyk_core::MassFlow;
///
/// let m = MassFlow::from_kg_per_s(0.5);
/// assert_eq!(m.to_kg_per_s(), 0.5);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct MassFlow(pub f64);

impl MassFlow {
    /// Creates a MassFlow from a value in kilograms per second.
    pub fn from_kg_per_s(value: f64) -> Self {
        MassFlow(value)
    }

    /// Creates a MassFlow from a value in grams per second.
    pub fn from_grams_per_s(value: f64) -> Self {
        MassFlow(value / 1_000.0)
    }

    /// Returns the mass flow rate in kilograms per second.
    pub fn to_kg_per_s(&self) -> f64 {
        self.0
    }

    /// Returns the mass flow rate in grams per second.
    pub fn to_grams_per_s(&self) -> f64 {
        self.0 * 1_000.0
    }

    /// Returns mass flow clamped to at least [`MIN_MASS_FLOW_REGULARIZATION_KG_S`] for use in denominators.
    ///
    /// Use this whenever dividing by mass flow (e.g. Q/ṁ) or by a quantity derived from it (e.g. Re ∝ ṁ)
    /// to avoid division by zero when the branch has zero flow (e.g. component in Off state).
    #[must_use]
    pub fn regularized(self) -> Self {
        MassFlow(self.0.max(MIN_MASS_FLOW_REGULARIZATION_KG_S))
    }
}

impl fmt::Display for MassFlow {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} kg/s", self.0)
    }
}

impl From<f64> for MassFlow {
    fn from(value: f64) -> Self {
        MassFlow(value)
    }
}

impl Add<MassFlow> for MassFlow {
    type Output = MassFlow;

    fn add(self, other: MassFlow) -> MassFlow {
        MassFlow(self.0 + other.0)
    }
}

impl Sub<MassFlow> for MassFlow {
    type Output = MassFlow;

    fn sub(self, other: MassFlow) -> MassFlow {
        MassFlow(self.0 - other.0)
    }
}

impl Mul<f64> for MassFlow {
    type Output = MassFlow;

    fn mul(self, scalar: f64) -> MassFlow {
        MassFlow(self.0 * scalar)
    }
}

impl Mul<MassFlow> for f64 {
    type Output = MassFlow;

    fn mul(self, m: MassFlow) -> MassFlow {
        MassFlow(self * m.0)
    }
}

impl Div<f64> for MassFlow {
    type Output = MassFlow;

    fn div(self, scalar: f64) -> MassFlow {
        MassFlow(self.0 / scalar)
    }
}

/// Power in Watts (W).
///
/// Internally stores the value in Watts (SI base unit).
/// Provides conversions to/from common units like kilowatts.
///
/// # Example
///
/// ```
/// use entropyk_core::Power;
///
/// let p = Power::from_kilowatts(1.0);
/// assert_eq!(p.to_watts(), 1000.0);
/// assert_eq!(p.to_kilowatts(), 1.0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Power(pub f64);

impl Power {
    /// Creates a Power from a value in Watts.
    pub fn from_watts(value: f64) -> Self {
        Power(value)
    }

    /// Creates a Power from a value in kilowatts.
    pub fn from_kilowatts(value: f64) -> Self {
        Power(value * 1_000.0)
    }

    /// Creates a Power from a value in megawatts.
    pub fn from_megawatts(value: f64) -> Self {
        Power(value * 1_000_000.0)
    }

    /// Returns the power in Watts.
    pub fn to_watts(&self) -> f64 {
        self.0
    }

    /// Returns the power in kilowatts.
    pub fn to_kilowatts(&self) -> f64 {
        self.0 / 1_000.0
    }

    /// Returns the power in megawatts.
    pub fn to_megawatts(&self) -> f64 {
        self.0 / 1_000_000.0
    }
}

impl fmt::Display for Power {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} W", self.0)
    }
}

impl From<f64> for Power {
    fn from(value: f64) -> Self {
        Power(value)
    }
}

impl Add<Power> for Power {
    type Output = Power;

    fn add(self, other: Power) -> Power {
        Power(self.0 + other.0)
    }
}

impl Sub<Power> for Power {
    type Output = Power;

    fn sub(self, other: Power) -> Power {
        Power(self.0 - other.0)
    }
}

impl Mul<f64> for Power {
    type Output = Power;

    fn mul(self, scalar: f64) -> Power {
        Power(self.0 * scalar)
    }
}

impl Mul<Power> for f64 {
    type Output = Power;

    fn mul(self, p: Power) -> Power {
        Power(self * p.0)
    }
}

impl Div<f64> for Power {
    type Output = Power;

    fn div(self, scalar: f64) -> Power {
        Power(self.0 / scalar)
    }
}

/// Concentration (dimensionless fraction 0.0 to 1.0).
///
/// Represents glycol/brine mixture fraction. Internally stores a dimensionless
/// fraction clamped to [0.0, 1.0].
///
/// # Example
///
/// ```
/// use entropyk_core::Concentration;
///
/// let c = Concentration::from_percent(50.0);
/// assert_eq!(c.to_fraction(), 0.5);
/// assert_eq!(c.to_percent(), 50.0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Concentration(pub f64);

impl Concentration {
    /// Creates a Concentration from a fraction, clamped to [0.0, 1.0].
    pub fn from_fraction(value: f64) -> Self {
        Concentration(value.clamp(0.0, 1.0))
    }

    /// Creates a Concentration from a percentage, clamped to [0, 100]%.
    pub fn from_percent(value: f64) -> Self {
        Concentration((value / 100.0).clamp(0.0, 1.0))
    }

    /// Returns the concentration as a fraction [0.0, 1.0].
    pub fn to_fraction(&self) -> f64 {
        self.0
    }

    /// Returns the concentration as a percentage [0, 100].
    pub fn to_percent(&self) -> f64 {
        self.0 * 100.0
    }
}

impl fmt::Display for Concentration {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}%", self.to_percent())
    }
}

impl From<f64> for Concentration {
    fn from(value: f64) -> Self {
        Concentration(value.clamp(0.0, 1.0))
    }
}

impl Add<Concentration> for Concentration {
    type Output = Concentration;

    fn add(self, other: Concentration) -> Concentration {
        Concentration((self.0 + other.0).clamp(0.0, 1.0))
    }
}

impl Sub<Concentration> for Concentration {
    type Output = Concentration;

    fn sub(self, other: Concentration) -> Concentration {
        Concentration((self.0 - other.0).clamp(0.0, 1.0))
    }
}

impl Mul<f64> for Concentration {
    type Output = Concentration;

    fn mul(self, scalar: f64) -> Concentration {
        Concentration((self.0 * scalar).clamp(0.0, 1.0))
    }
}

impl Mul<Concentration> for f64 {
    type Output = Concentration;

    fn mul(self, c: Concentration) -> Concentration {
        Concentration((self * c.0).clamp(0.0, 1.0))
    }
}

impl Div<f64> for Concentration {
    type Output = Concentration;

    fn div(self, scalar: f64) -> Concentration {
        Concentration((self.0 / scalar).clamp(0.0, 1.0))
    }
}

/// Volumetric flow rate in cubic meters per second (m³/s).
///
/// Internally stores the value in m³/s (SI base unit).
/// Provides conversions to/from L/s, L/min, and m³/h.
///
/// Note: Unlike bounded types (Concentration, RelativeHumidity, VaporQuality),
/// VolumeFlow accepts negative values to allow representation of reverse flow.
///
/// # Example
///
/// ```
/// use entropyk_core::VolumeFlow;
///
/// let v = VolumeFlow::from_l_per_s(100.0);
/// assert_eq!(v.to_m3_per_s(), 0.1);
/// assert_eq!(v.to_l_per_min(), 6000.0);
///
/// // Negative values represent reverse flow
/// let reverse = VolumeFlow::from_m3_per_s(-0.5);
/// assert_eq!(reverse.to_m3_per_s(), -0.5);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct VolumeFlow(pub f64);

impl VolumeFlow {
    /// Creates a VolumeFlow from a value in m³/s.
    pub fn from_m3_per_s(value: f64) -> Self {
        VolumeFlow(value)
    }

    /// Creates a VolumeFlow from a value in liters per second.
    pub fn from_l_per_s(value: f64) -> Self {
        VolumeFlow(value / 1000.0)
    }

    /// Creates a VolumeFlow from a value in liters per minute.
    pub fn from_l_per_min(value: f64) -> Self {
        VolumeFlow(value / 60_000.0)
    }

    /// Creates a VolumeFlow from a value in m³/h.
    pub fn from_m3_per_h(value: f64) -> Self {
        VolumeFlow(value / 3600.0)
    }

    /// Returns the volumetric flow in m³/s.
    pub fn to_m3_per_s(&self) -> f64 {
        self.0
    }

    /// Returns the volumetric flow in liters per second.
    pub fn to_l_per_s(&self) -> f64 {
        self.0 * 1000.0
    }

    /// Returns the volumetric flow in liters per minute.
    pub fn to_l_per_min(&self) -> f64 {
        self.0 * 60_000.0
    }

    /// Returns the volumetric flow in m³/h.
    pub fn to_m3_per_h(&self) -> f64 {
        self.0 * 3600.0
    }
}

impl fmt::Display for VolumeFlow {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} m³/s", self.0)
    }
}

impl From<f64> for VolumeFlow {
    fn from(value: f64) -> Self {
        VolumeFlow(value)
    }
}

impl Add<VolumeFlow> for VolumeFlow {
    type Output = VolumeFlow;

    fn add(self, other: VolumeFlow) -> VolumeFlow {
        VolumeFlow(self.0 + other.0)
    }
}

impl Sub<VolumeFlow> for VolumeFlow {
    type Output = VolumeFlow;

    fn sub(self, other: VolumeFlow) -> VolumeFlow {
        VolumeFlow(self.0 - other.0)
    }
}

impl Mul<f64> for VolumeFlow {
    type Output = VolumeFlow;

    fn mul(self, scalar: f64) -> VolumeFlow {
        VolumeFlow(self.0 * scalar)
    }
}

impl Mul<VolumeFlow> for f64 {
    type Output = VolumeFlow;

    fn mul(self, v: VolumeFlow) -> VolumeFlow {
        VolumeFlow(self * v.0)
    }
}

impl Div<f64> for VolumeFlow {
    type Output = VolumeFlow;

    fn div(self, scalar: f64) -> VolumeFlow {
        VolumeFlow(self.0 / scalar)
    }
}

/// Relative humidity (dimensionless fraction 0.0 to 1.0).
///
/// Represents air moisture level. Internally stores a dimensionless
/// fraction clamped to [0.0, 1.0].
///
/// # Example
///
/// ```
/// use entropyk_core::RelativeHumidity;
///
/// let rh = RelativeHumidity::from_percent(60.0);
/// assert_eq!(rh.to_fraction(), 0.6);
/// assert_eq!(rh.to_percent(), 60.0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct RelativeHumidity(pub f64);

impl RelativeHumidity {
    /// Creates a RelativeHumidity from a fraction, clamped to [0.0, 1.0].
    pub fn from_fraction(value: f64) -> Self {
        RelativeHumidity(value.clamp(0.0, 1.0))
    }

    /// Creates a RelativeHumidity from a percentage, clamped to [0, 100]%.
    pub fn from_percent(value: f64) -> Self {
        RelativeHumidity((value / 100.0).clamp(0.0, 1.0))
    }

    /// Returns the relative humidity as a fraction [0.0, 1.0].
    pub fn to_fraction(&self) -> f64 {
        self.0
    }

    /// Returns the relative humidity as a percentage [0, 100].
    pub fn to_percent(&self) -> f64 {
        self.0 * 100.0
    }
}

impl fmt::Display for RelativeHumidity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}% RH", self.to_percent())
    }
}

impl From<f64> for RelativeHumidity {
    fn from(value: f64) -> Self {
        RelativeHumidity(value.clamp(0.0, 1.0))
    }
}

impl Add<RelativeHumidity> for RelativeHumidity {
    type Output = RelativeHumidity;

    fn add(self, other: RelativeHumidity) -> RelativeHumidity {
        RelativeHumidity((self.0 + other.0).clamp(0.0, 1.0))
    }
}

impl Sub<RelativeHumidity> for RelativeHumidity {
    type Output = RelativeHumidity;

    fn sub(self, other: RelativeHumidity) -> RelativeHumidity {
        RelativeHumidity((self.0 - other.0).clamp(0.0, 1.0))
    }
}

impl Mul<f64> for RelativeHumidity {
    type Output = RelativeHumidity;

    fn mul(self, scalar: f64) -> RelativeHumidity {
        RelativeHumidity((self.0 * scalar).clamp(0.0, 1.0))
    }
}

impl Mul<RelativeHumidity> for f64 {
    type Output = RelativeHumidity;

    fn mul(self, rh: RelativeHumidity) -> RelativeHumidity {
        RelativeHumidity((self * rh.0).clamp(0.0, 1.0))
    }
}

impl Div<f64> for RelativeHumidity {
    type Output = RelativeHumidity;

    fn div(self, scalar: f64) -> RelativeHumidity {
        RelativeHumidity((self.0 / scalar).clamp(0.0, 1.0))
    }
}

/// Vapor quality (dimensionless fraction 0.0 to 1.0).
///
/// Represents refrigerant two-phase state where 0 = saturated liquid
/// and 1 = saturated vapor. Internally stores a dimensionless fraction
/// clamped to [0.0, 1.0].
///
/// # Example
///
/// ```
/// use entropyk_core::VaporQuality;
///
/// let q = VaporQuality::SATURATED_VAPOR;
/// assert!(q.is_saturated_vapor());
///
/// let q2 = VaporQuality::from_fraction(0.5);
/// assert_eq!(q2.to_percent(), 50.0);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct VaporQuality(pub f64);

impl VaporQuality {
    /// Saturated liquid quality (0.0).
    pub const SATURATED_LIQUID: VaporQuality = VaporQuality(0.0);
    /// Saturated vapor quality (1.0).
    pub const SATURATED_VAPOR: VaporQuality = VaporQuality(1.0);

    /// Tolerance for saturated state detection.
    const SATURATED_TOLERANCE: f64 = 1e-9;

    /// Creates a VaporQuality from a fraction, clamped to [0.0, 1.0].
    pub fn from_fraction(value: f64) -> Self {
        VaporQuality(value.clamp(0.0, 1.0))
    }

    /// Creates a VaporQuality from a percentage, clamped to [0, 100]%.
    pub fn from_percent(value: f64) -> Self {
        VaporQuality((value / 100.0).clamp(0.0, 1.0))
    }

    /// Returns the vapor quality as a fraction [0.0, 1.0].
    pub fn to_fraction(&self) -> f64 {
        self.0
    }

    /// Returns the vapor quality as a percentage [0, 100].
    pub fn to_percent(&self) -> f64 {
        self.0 * 100.0
    }

    /// Returns true if this represents saturated liquid (quality ≈ 0).
    pub fn is_saturated_liquid(&self) -> bool {
        self.0.abs() < Self::SATURATED_TOLERANCE
    }

    /// Returns true if this represents saturated vapor (quality ≈ 1).
    pub fn is_saturated_vapor(&self) -> bool {
        (1.0 - self.0).abs() < Self::SATURATED_TOLERANCE
    }
}

impl fmt::Display for VaporQuality {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} (quality)", self.0)
    }
}

impl From<f64> for VaporQuality {
    fn from(value: f64) -> Self {
        VaporQuality(value.clamp(0.0, 1.0))
    }
}

impl Add<VaporQuality> for VaporQuality {
    type Output = VaporQuality;

    fn add(self, other: VaporQuality) -> VaporQuality {
        VaporQuality((self.0 + other.0).clamp(0.0, 1.0))
    }
}

impl Sub<VaporQuality> for VaporQuality {
    type Output = VaporQuality;

    fn sub(self, other: VaporQuality) -> VaporQuality {
        VaporQuality((self.0 - other.0).clamp(0.0, 1.0))
    }
}

impl Mul<f64> for VaporQuality {
    type Output = VaporQuality;

    fn mul(self, scalar: f64) -> VaporQuality {
        VaporQuality((self.0 * scalar).clamp(0.0, 1.0))
    }
}

impl Mul<VaporQuality> for f64 {
    type Output = VaporQuality;

    fn mul(self, q: VaporQuality) -> VaporQuality {
        VaporQuality((self * q.0).clamp(0.0, 1.0))
    }
}

impl Div<f64> for VaporQuality {
    type Output = VaporQuality;

    fn div(self, scalar: f64) -> VaporQuality {
        VaporQuality((self.0 / scalar).clamp(0.0, 1.0))
    }
}

/// Entropy in J/(kg·K).
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Entropy(pub f64);

impl Entropy {
    /// Creates entropy from J/(kg·K).
    pub fn from_joules_per_kg_kelvin(value: f64) -> Self {
        Entropy(value)
    }

    /// Returns entropy in J/(kg·K).
    pub fn to_joules_per_kg_kelvin(&self) -> f64 {
        self.0
    }
}

impl From<f64> for Entropy {
    fn from(value: f64) -> Self {
        Entropy(value)
    }
}

impl fmt::Display for Entropy {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} J/(kg·K)", self.0)
    }
}

impl Add<Entropy> for Entropy {
    type Output = Entropy;

    fn add(self, other: Entropy) -> Entropy {
        Entropy(self.0 + other.0)
    }
}

impl Sub<Entropy> for Entropy {
    type Output = Entropy;

    fn sub(self, other: Entropy) -> Entropy {
        Entropy(self.0 - other.0)
    }
}

impl Mul<f64> for Entropy {
    type Output = Entropy;

    fn mul(self, scalar: f64) -> Entropy {
        Entropy(self.0 * scalar)
    }
}

impl Mul<Entropy> for f64 {
    type Output = Entropy;

    fn mul(self, s: Entropy) -> Entropy {
        Entropy(self * s.0)
    }
}

impl Div<f64> for Entropy {
    type Output = Entropy;

    fn div(self, scalar: f64) -> Entropy {
        Entropy(self.0 / scalar)
    }
}

/// Thermal conductance in Watts per Kelvin (W/K).
///
/// Represents the heat transfer coefficient (UA value) for thermal coupling
/// between circuits or components.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct ThermalConductance(pub f64);

impl ThermalConductance {
    /// Creates a ThermalConductance from a value in Watts per Kelvin (W/K).
    pub fn from_watts_per_kelvin(value: f64) -> Self {
        ThermalConductance(value)
    }

    /// Creates a ThermalConductance from a value in kilowatts per Kelvin (kW/K).
    pub fn from_kilowatts_per_kelvin(value: f64) -> Self {
        ThermalConductance(value * 1_000.0)
    }

    /// Returns the thermal conductance in Watts per Kelvin.
    pub fn to_watts_per_kelvin(&self) -> f64 {
        self.0
    }

    /// Returns the thermal conductance in kilowatts per Kelvin.
    pub fn to_kilowatts_per_kelvin(&self) -> f64 {
        self.0 / 1_000.0
    }
}

impl fmt::Display for ThermalConductance {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} W/K", self.0)
    }
}

impl From<f64> for ThermalConductance {
    fn from(value: f64) -> Self {
        ThermalConductance(value)
    }
}

/// Circuit identifier for multi-circuit thermodynamic systems.
///
/// Represents a unique identifier for a circuit within a multi-circuit machine.
/// Uses a compact `u8` representation for performance, allowing up to 256 circuits.
///
/// # Creation
///
/// - From number: `CircuitId::from_number(5)` or `CircuitId::from(5u8)`
/// - From string: `CircuitId::from("primary")` (uses hash-based conversion)
///
/// # Example
///
/// ```
/// use entropyk_core::CircuitId;
///
/// let id = CircuitId::from_number(3);
/// assert_eq!(id.as_number(), 3);
///
/// let from_str: CircuitId = "primary".into();
/// let same: CircuitId = "primary".into();
/// assert_eq!(from_str, same); // Deterministic hashing
/// ```
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize,
)]
pub struct CircuitId(pub u16);

impl CircuitId {
    /// Maximum possible circuit identifier.
    pub const MAX: u16 = 65535;
    /// Primary circuit identifier.
    pub const ZERO: CircuitId = CircuitId(0);

    /// Creates a new circuit identifier from a raw number.
    pub fn from_number(n: u16) -> Self {
        Self(n)
    }

    /// Returns the raw numeric representation.
    pub fn as_number(&self) -> u16 {
        self.0
    }
}

impl From<u8> for CircuitId {
    fn from(n: u8) -> Self {
        Self(n as u16)
    }
}

impl From<u16> for CircuitId {
    fn from(n: u16) -> Self {
        Self(n)
    }
}

impl From<&str> for CircuitId {
    fn from(s: &str) -> Self {
        let hash = seahash::hash(s.as_bytes());
        Self(hash as u16)
    }
}

impl From<String> for CircuitId {
    fn from(s: String) -> Self {
        Self::from(s.as_str())
    }
}

impl std::fmt::Display for CircuitId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Circuit-{}", self.0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use approx::assert_relative_eq;

    // ==================== CONCENTRATION TESTS ====================

    #[test]
    fn test_concentration_from_fraction() {
        let c = Concentration::from_fraction(0.5);
        assert_relative_eq!(c.0, 0.5, epsilon = 1e-10);
        assert_relative_eq!(c.to_fraction(), 0.5, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_from_percent() {
        let c = Concentration::from_percent(50.0);
        assert_relative_eq!(c.to_fraction(), 0.5, epsilon = 1e-10);
        assert_relative_eq!(c.to_percent(), 50.0, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_clamping_negative() {
        let c = Concentration::from_fraction(-0.5);
        assert_relative_eq!(c.to_fraction(), 0.0, epsilon = 1e-10);

        let c2 = Concentration::from_percent(-10.0);
        assert_relative_eq!(c2.to_fraction(), 0.0, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_clamping_over_one() {
        let c = Concentration::from_fraction(1.5);
        assert_relative_eq!(c.to_fraction(), 1.0, epsilon = 1e-10);

        let c2 = Concentration::from_percent(150.0);
        assert_relative_eq!(c2.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_display() {
        let c = Concentration::from_fraction(0.5);
        assert_eq!(format!("{}", c), "50%");
    }

    #[test]
    fn test_concentration_from_f64() {
        let c: Concentration = 0.5.into();
        assert_relative_eq!(c.to_fraction(), 0.5, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_from_f64_clamping() {
        let c: Concentration = (-0.5).into();
        assert_relative_eq!(c.to_fraction(), 0.0, epsilon = 1e-10);

        let c2: Concentration = 1.5.into();
        assert_relative_eq!(c2.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_add() {
        let c1 = Concentration::from_fraction(0.3);
        let c2 = Concentration::from_fraction(0.4);
        let c3 = c1 + c2;
        assert_relative_eq!(c3.to_fraction(), 0.7, epsilon = 1e-10);

        // Test clamping on overflow
        let c4 = Concentration::from_fraction(0.8);
        let c5 = Concentration::from_fraction(0.5);
        let c6 = c4 + c5;
        assert_relative_eq!(c6.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_sub() {
        let c1 = Concentration::from_fraction(0.7);
        let c2 = Concentration::from_fraction(0.3);
        let c3 = c1 - c2;
        assert_relative_eq!(c3.to_fraction(), 0.4, epsilon = 1e-10);

        // Test clamping on underflow
        let c4 = Concentration::from_fraction(0.2);
        let c5 = Concentration::from_fraction(0.5);
        let c6 = c4 - c5;
        assert_relative_eq!(c6.to_fraction(), 0.0, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_mul() {
        let c = Concentration::from_fraction(0.5);
        let c2 = c * 2.0;
        assert_relative_eq!(c2.to_fraction(), 1.0, epsilon = 1e-10);

        // Test reverse multiplication
        let c3 = 2.0 * c;
        assert_relative_eq!(c3.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_div() {
        let c = Concentration::from_fraction(0.8);
        let c2 = c / 2.0;
        assert_relative_eq!(c2.to_fraction(), 0.4, epsilon = 1e-10);
    }

    #[test]
    fn test_concentration_edge_cases() {
        let zero = Concentration::from_fraction(0.0);
        assert_relative_eq!(zero.to_fraction(), 0.0, epsilon = 1e-10);
        assert_relative_eq!(zero.to_percent(), 0.0, epsilon = 1e-10);

        let one = Concentration::from_fraction(1.0);
        assert_relative_eq!(one.to_fraction(), 1.0, epsilon = 1e-10);
        assert_relative_eq!(one.to_percent(), 100.0, epsilon = 1e-10);
    }

    // ==================== VOLUME FLOW TESTS ====================

    #[test]
    fn test_volume_flow_from_m3_per_s() {
        let v = VolumeFlow::from_m3_per_s(1.0);
        assert_relative_eq!(v.0, 1.0, epsilon = 1e-10);
        assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_from_l_per_s() {
        let v = VolumeFlow::from_l_per_s(1000.0);
        assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
        assert_relative_eq!(v.to_l_per_s(), 1000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_from_l_per_min() {
        let v = VolumeFlow::from_l_per_min(60_000.0);
        assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
        assert_relative_eq!(v.to_l_per_min(), 60_000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_from_m3_per_h() {
        let v = VolumeFlow::from_m3_per_h(3600.0);
        assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
        assert_relative_eq!(v.to_m3_per_h(), 3600.0, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_round_trip() {
        let v1 = VolumeFlow::from_l_per_s(50.0);
        assert_relative_eq!(v1.to_l_per_s(), 50.0, epsilon = 1e-10);

        let v2 = VolumeFlow::from_l_per_min(3000.0);
        assert_relative_eq!(v2.to_l_per_min(), 3000.0, epsilon = 1e-10);

        let v3 = VolumeFlow::from_m3_per_h(100.0);
        assert_relative_eq!(v3.to_m3_per_h(), 100.0, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_cross_conversions() {
        // 1 m³/s = 1000 L/s = 60000 L/min = 3600 m³/h
        let v = VolumeFlow::from_m3_per_s(1.0);
        assert_relative_eq!(v.to_l_per_s(), 1000.0, epsilon = 1e-10);
        assert_relative_eq!(v.to_l_per_min(), 60_000.0, epsilon = 1e-10);
        assert_relative_eq!(v.to_m3_per_h(), 3600.0, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_display() {
        let v = VolumeFlow::from_m3_per_s(0.5);
        assert_eq!(format!("{}", v), "0.5 m³/s");
    }

    #[test]
    fn test_volume_flow_from_f64() {
        let v: VolumeFlow = 1.0.into();
        assert_relative_eq!(v.to_m3_per_s(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_add() {
        let v1 = VolumeFlow::from_m3_per_s(1.0);
        let v2 = VolumeFlow::from_m3_per_s(0.5);
        let v3 = v1 + v2;
        assert_relative_eq!(v3.to_m3_per_s(), 1.5, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_sub() {
        let v1 = VolumeFlow::from_m3_per_s(1.0);
        let v2 = VolumeFlow::from_m3_per_s(0.3);
        let v3 = v1 - v2;
        assert_relative_eq!(v3.to_m3_per_s(), 0.7, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_mul() {
        let v = VolumeFlow::from_m3_per_s(0.5);
        let v2 = v * 2.0;
        assert_relative_eq!(v2.to_m3_per_s(), 1.0, epsilon = 1e-10);

        let v3 = 2.0 * v;
        assert_relative_eq!(v3.to_m3_per_s(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_div() {
        let v = VolumeFlow::from_m3_per_s(1.0);
        let v2 = v / 4.0;
        assert_relative_eq!(v2.to_m3_per_s(), 0.25, epsilon = 1e-10);
    }

    #[test]
    fn test_volume_flow_edge_cases() {
        let zero = VolumeFlow::from_m3_per_s(0.0);
        assert_relative_eq!(zero.to_m3_per_s(), 0.0, epsilon = 1e-10);

        let negative = VolumeFlow::from_m3_per_s(-1.0);
        assert_relative_eq!(negative.to_m3_per_s(), -1.0, epsilon = 1e-10);
    }

    // ==================== RELATIVE HUMIDITY TESTS ====================

    #[test]
    fn test_relative_humidity_from_fraction() {
        let rh = RelativeHumidity::from_fraction(0.6);
        assert_relative_eq!(rh.0, 0.6, epsilon = 1e-10);
        assert_relative_eq!(rh.to_fraction(), 0.6, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_from_percent() {
        let rh = RelativeHumidity::from_percent(60.0);
        assert_relative_eq!(rh.to_fraction(), 0.6, epsilon = 1e-10);
        assert_relative_eq!(rh.to_percent(), 60.0, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_clamping_negative() {
        let rh = RelativeHumidity::from_fraction(-0.5);
        assert_relative_eq!(rh.to_fraction(), 0.0, epsilon = 1e-10);

        let rh2 = RelativeHumidity::from_percent(-10.0);
        assert_relative_eq!(rh2.to_fraction(), 0.0, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_clamping_over_one() {
        let rh = RelativeHumidity::from_fraction(1.5);
        assert_relative_eq!(rh.to_fraction(), 1.0, epsilon = 1e-10);

        let rh2 = RelativeHumidity::from_percent(150.0);
        assert_relative_eq!(rh2.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_display() {
        let rh = RelativeHumidity::from_fraction(0.6);
        assert_eq!(format!("{}", rh), "60% RH");
    }

    #[test]
    fn test_relative_humidity_from_f64() {
        let rh: RelativeHumidity = 0.6.into();
        assert_relative_eq!(rh.to_fraction(), 0.6, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_from_f64_clamping() {
        let rh: RelativeHumidity = (-0.5).into();
        assert_relative_eq!(rh.to_fraction(), 0.0, epsilon = 1e-10);

        let rh2: RelativeHumidity = 1.5.into();
        assert_relative_eq!(rh2.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_add() {
        let rh1 = RelativeHumidity::from_fraction(0.3);
        let rh2 = RelativeHumidity::from_fraction(0.4);
        let rh3 = rh1 + rh2;
        assert_relative_eq!(rh3.to_fraction(), 0.7, epsilon = 1e-10);

        let rh4 = RelativeHumidity::from_fraction(0.8);
        let rh5 = RelativeHumidity::from_fraction(0.5);
        let rh6 = rh4 + rh5;
        assert_relative_eq!(rh6.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_sub() {
        let rh1 = RelativeHumidity::from_fraction(0.7);
        let rh2 = RelativeHumidity::from_fraction(0.3);
        let rh3 = rh1 - rh2;
        assert_relative_eq!(rh3.to_fraction(), 0.4, epsilon = 1e-10);

        let rh4 = RelativeHumidity::from_fraction(0.2);
        let rh5 = RelativeHumidity::from_fraction(0.5);
        let rh6 = rh4 - rh5;
        assert_relative_eq!(rh6.to_fraction(), 0.0, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_mul() {
        let rh = RelativeHumidity::from_fraction(0.5);
        let rh2 = rh * 2.0;
        assert_relative_eq!(rh2.to_fraction(), 1.0, epsilon = 1e-10);

        let rh3 = 2.0 * rh;
        assert_relative_eq!(rh3.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_div() {
        let rh = RelativeHumidity::from_fraction(0.8);
        let rh2 = rh / 2.0;
        assert_relative_eq!(rh2.to_fraction(), 0.4, epsilon = 1e-10);
    }

    #[test]
    fn test_relative_humidity_edge_cases() {
        let zero = RelativeHumidity::from_fraction(0.0);
        assert_relative_eq!(zero.to_fraction(), 0.0, epsilon = 1e-10);
        assert_relative_eq!(zero.to_percent(), 0.0, epsilon = 1e-10);

        let one = RelativeHumidity::from_fraction(1.0);
        assert_relative_eq!(one.to_fraction(), 1.0, epsilon = 1e-10);
        assert_relative_eq!(one.to_percent(), 100.0, epsilon = 1e-10);
    }

    // ==================== VAPOR QUALITY TESTS ====================

    #[test]
    fn test_vapor_quality_from_fraction() {
        let q = VaporQuality::from_fraction(0.5);
        assert_relative_eq!(q.0, 0.5, epsilon = 1e-10);
        assert_relative_eq!(q.to_fraction(), 0.5, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_from_percent() {
        let q = VaporQuality::from_percent(50.0);
        assert_relative_eq!(q.to_fraction(), 0.5, epsilon = 1e-10);
        assert_relative_eq!(q.to_percent(), 50.0, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_constants() {
        assert_relative_eq!(VaporQuality::SATURATED_LIQUID.0, 0.0, epsilon = 1e-10);
        assert_relative_eq!(VaporQuality::SATURATED_VAPOR.0, 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_is_saturated_liquid() {
        let q = VaporQuality::SATURATED_LIQUID;
        assert!(q.is_saturated_liquid());
        assert!(!q.is_saturated_vapor());

        let q2 = VaporQuality::from_fraction(1e-10);
        assert!(q2.is_saturated_liquid());

        let q3 = VaporQuality::from_fraction(0.001);
        assert!(!q3.is_saturated_liquid());
    }

    #[test]
    fn test_vapor_quality_is_saturated_vapor() {
        let q = VaporQuality::SATURATED_VAPOR;
        assert!(q.is_saturated_vapor());
        assert!(!q.is_saturated_liquid());

        let q2 = VaporQuality::from_fraction(1.0 - 1e-10);
        assert!(q2.is_saturated_vapor());

        let q3 = VaporQuality::from_fraction(0.999);
        assert!(!q3.is_saturated_vapor());
    }

    #[test]
    fn test_vapor_quality_clamping_negative() {
        let q = VaporQuality::from_fraction(-0.5);
        assert_relative_eq!(q.to_fraction(), 0.0, epsilon = 1e-10);

        let q2 = VaporQuality::from_percent(-10.0);
        assert_relative_eq!(q2.to_fraction(), 0.0, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_clamping_over_one() {
        let q = VaporQuality::from_fraction(1.5);
        assert_relative_eq!(q.to_fraction(), 1.0, epsilon = 1e-10);

        let q2 = VaporQuality::from_percent(150.0);
        assert_relative_eq!(q2.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_display() {
        let q = VaporQuality::from_fraction(0.5);
        assert_eq!(format!("{}", q), "0.5 (quality)");
    }

    #[test]
    fn test_vapor_quality_from_f64() {
        let q: VaporQuality = 0.5.into();
        assert_relative_eq!(q.to_fraction(), 0.5, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_from_f64_clamping() {
        let q: VaporQuality = (-0.5).into();
        assert_relative_eq!(q.to_fraction(), 0.0, epsilon = 1e-10);

        let q2: VaporQuality = 1.5.into();
        assert_relative_eq!(q2.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_add() {
        let q1 = VaporQuality::from_fraction(0.3);
        let q2 = VaporQuality::from_fraction(0.4);
        let q3 = q1 + q2;
        assert_relative_eq!(q3.to_fraction(), 0.7, epsilon = 1e-10);

        let q4 = VaporQuality::from_fraction(0.8);
        let q5 = VaporQuality::from_fraction(0.5);
        let q6 = q4 + q5;
        assert_relative_eq!(q6.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_sub() {
        let q1 = VaporQuality::from_fraction(0.7);
        let q2 = VaporQuality::from_fraction(0.3);
        let q3 = q1 - q2;
        assert_relative_eq!(q3.to_fraction(), 0.4, epsilon = 1e-10);

        let q4 = VaporQuality::from_fraction(0.2);
        let q5 = VaporQuality::from_fraction(0.5);
        let q6 = q4 - q5;
        assert_relative_eq!(q6.to_fraction(), 0.0, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_mul() {
        let q = VaporQuality::from_fraction(0.5);
        let q2 = q * 2.0;
        assert_relative_eq!(q2.to_fraction(), 1.0, epsilon = 1e-10);

        let q3 = 2.0 * q;
        assert_relative_eq!(q3.to_fraction(), 1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_div() {
        let q = VaporQuality::from_fraction(0.8);
        let q2 = q / 2.0;
        assert_relative_eq!(q2.to_fraction(), 0.4, epsilon = 1e-10);
    }

    #[test]
    fn test_vapor_quality_edge_cases() {
        let zero = VaporQuality::from_fraction(0.0);
        assert_relative_eq!(zero.to_fraction(), 0.0, epsilon = 1e-10);
        assert_relative_eq!(zero.to_percent(), 0.0, epsilon = 1e-10);
        assert!(zero.is_saturated_liquid());

        let one = VaporQuality::from_fraction(1.0);
        assert_relative_eq!(one.to_fraction(), 1.0, epsilon = 1e-10);
        assert_relative_eq!(one.to_percent(), 100.0, epsilon = 1e-10);
        assert!(one.is_saturated_vapor());
    }

    // ==================== PRESSURE TESTS ====================

    #[test]
    fn test_pressure_from_pascals() {
        let p = Pressure::from_pascals(101325.0);
        assert_relative_eq!(p.0, 101325.0, epsilon = 1e-10);
        assert_relative_eq!(p.to_pascals(), 101325.0, epsilon = 1e-10);
    }

    #[test]
    fn test_pressure_from_bar() {
        let p = Pressure::from_bar(1.0);
        assert_relative_eq!(p.to_pascals(), 100_000.0, epsilon = 1e-6);
    }

    #[test]
    fn test_pressure_to_bar() {
        let p = Pressure::from_pascals(100_000.0);
        assert_relative_eq!(p.to_bar(), 1.0, epsilon = 1e-6);
    }

    #[test]
    fn test_pressure_round_trip() {
        let p1 = Pressure::from_bar(2.5);
        let bar = p1.to_bar();
        assert_relative_eq!(bar, 2.5, epsilon = 1e-6);
    }

    #[test]
    fn test_pressure_display() {
        let p = Pressure::from_pascals(101325.0);
        assert_eq!(format!("{}", p), "101325 Pa");
    }

    #[test]
    fn test_pressure_from_f64() {
        let p: Pressure = 101325.0.into();
        assert_relative_eq!(p.to_pascals(), 101325.0, epsilon = 1e-10);
    }

    #[test]
    fn test_pressure_add() {
        let p1 = Pressure::from_pascals(100_000.0);
        let p2 = Pressure::from_pascals(50_000.0);
        let p3 = p1 + p2;
        assert_relative_eq!(p3.to_pascals(), 150_000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_pressure_sub() {
        let p1 = Pressure::from_pascals(100_000.0);
        let p2 = Pressure::from_pascals(30_000.0);
        let p3 = p1 - p2;
        assert_relative_eq!(p3.to_pascals(), 70_000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_pressure_mul() {
        let p = Pressure::from_pascals(50_000.0);
        let p2 = p * 2.0;
        assert_relative_eq!(p2.to_pascals(), 100_000.0, epsilon = 1e-10);

        // Test reverse multiplication
        let p3 = 2.0 * p;
        assert_relative_eq!(p3.to_pascals(), 100_000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_pressure_div() {
        let p = Pressure::from_pascals(100_000.0);
        let p2 = p / 2.0;
        assert_relative_eq!(p2.to_pascals(), 50_000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_pressure_psi_conversions() {
        // 1 atm = 14.696 psi ≈ 101325 Pa
        let p = Pressure::from_psi(14.696);
        assert_relative_eq!(p.to_pascals(), 101325.0, epsilon = 1.0);
        assert_relative_eq!(p.to_psi(), 14.696, epsilon = 1e-3);

        // Round trip test
        let p2 = Pressure::from_psi(100.0);
        assert_relative_eq!(p2.to_psi(), 100.0, epsilon = 1e-6);
    }

    // ==================== TEMPERATURE TESTS ====================

    #[test]
    fn test_temperature_from_kelvin() {
        let t = Temperature::from_kelvin(298.15);
        assert_relative_eq!(t.0, 298.15, epsilon = 1e-10);
        assert_relative_eq!(t.to_kelvin(), 298.15, epsilon = 1e-10);
    }

    #[test]
    fn test_temperature_from_celsius() {
        let t = Temperature::from_celsius(0.0);
        assert_relative_eq!(t.to_kelvin(), 273.15, epsilon = 1e-10);
    }

    #[test]
    fn test_temperature_to_celsius() {
        let t = Temperature::from_kelvin(273.15);
        assert_relative_eq!(t.to_celsius(), 0.0, epsilon = 1e-10);
    }

    #[test]
    fn test_temperature_round_trip() {
        let t1 = Temperature::from_celsius(25.0);
        let celsius = t1.to_celsius();
        assert_relative_eq!(celsius, 25.0, epsilon = 1e-10);
    }

    #[test]
    fn test_temperature_fahrenheit_conversions() {
        // 32°F = 0°C = 273.15K
        let t_freezing = Temperature::from_fahrenheit(32.0);
        assert_relative_eq!(t_freezing.to_celsius(), 0.0, epsilon = 1e-10);
        assert_relative_eq!(t_freezing.to_kelvin(), 273.15, epsilon = 1e-10);

        // 212°F = 100°C = 373.15K
        let t_boiling = Temperature::from_fahrenheit(212.0);
        assert_relative_eq!(t_boiling.to_celsius(), 100.0, epsilon = 1e-10);
        assert_relative_eq!(t_boiling.to_fahrenheit(), 212.0, epsilon = 1e-10);

        // Round trip
        let t1 = Temperature::from_kelvin(300.0);
        let f = t1.to_fahrenheit();
        let t2 = Temperature::from_fahrenheit(f);
        assert_relative_eq!(t1.to_kelvin(), t2.to_kelvin(), epsilon = 1e-10);
    }

    #[test]
    fn test_temperature_display() {
        let t = Temperature::from_kelvin(298.15);
        assert_eq!(format!("{}", t), "298.15 K");
    }

    #[test]
    fn test_temperature_add() {
        let t1 = Temperature::from_kelvin(300.0);
        let t2 = Temperature::from_kelvin(10.0);
        let t3 = t1 + t2;
        assert_relative_eq!(t3.to_kelvin(), 310.0, epsilon = 1e-10);
    }

    #[test]
    fn test_temperature_sub() {
        let t1 = Temperature::from_kelvin(300.0);
        let t2 = Temperature::from_kelvin(50.0);
        let t3 = t1 - t2;
        assert_relative_eq!(t3.to_kelvin(), 250.0, epsilon = 1e-10);
    }

    #[test]
    fn test_temperature_mul() {
        let t = Temperature::from_kelvin(100.0);
        let t2 = t * 2.0;
        assert_relative_eq!(t2.to_kelvin(), 200.0, epsilon = 1e-10);

        // Test reverse multiplication
        let t3 = 2.0 * t;
        assert_relative_eq!(t3.to_kelvin(), 200.0, epsilon = 1e-10);
    }

    #[test]
    fn test_temperature_div() {
        let t = Temperature::from_kelvin(300.0);
        let t2 = t / 3.0;
        assert_relative_eq!(t2.to_kelvin(), 100.0, epsilon = 1e-10);
    }

    // ==================== ENTHALPY TESTS ====================

    #[test]
    fn test_enthalpy_from_joules_per_kg() {
        let h = Enthalpy::from_joules_per_kg(1000.0);
        assert_relative_eq!(h.0, 1000.0, epsilon = 1e-10);
        assert_relative_eq!(h.to_joules_per_kg(), 1000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_enthalpy_display() {
        let h = Enthalpy::from_joules_per_kg(250000.0);
        assert_eq!(format!("{}", h), "250000 J/kg");
    }

    #[test]
    fn test_enthalpy_arithmetic() {
        let h1 = Enthalpy::from_joules_per_kg(1000.0);
        let h2 = Enthalpy::from_joules_per_kg(500.0);
        let h3 = h1 + h2;
        assert_relative_eq!(h3.to_joules_per_kg(), 1500.0, epsilon = 1e-10);

        let h4 = h1 - h2;
        assert_relative_eq!(h4.to_joules_per_kg(), 500.0, epsilon = 1e-10);

        let h5 = h1 * 2.0;
        assert_relative_eq!(h5.to_joules_per_kg(), 2000.0, epsilon = 1e-10);

        let h6 = h1 / 2.0;
        assert_relative_eq!(h6.to_joules_per_kg(), 500.0, epsilon = 1e-10);

        // Test reverse multiplication
        let h7 = 2.0 * h1;
        assert_relative_eq!(h7.to_joules_per_kg(), 2000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_enthalpy_unit_conversions() {
        let h1 = Enthalpy::from_kilojoules_per_kg(100.0);
        assert_relative_eq!(h1.to_joules_per_kg(), 100_000.0, epsilon = 1e-6);
        assert_relative_eq!(h1.to_kilojoules_per_kg(), 100.0, epsilon = 1e-6);
    }

    // ==================== MASS FLOW TESTS ====================

    #[test]
    fn test_mass_flow_from_kg_per_s() {
        let m = MassFlow::from_kg_per_s(0.5);
        assert_relative_eq!(m.0, 0.5, epsilon = 1e-10);
        assert_relative_eq!(m.to_kg_per_s(), 0.5, epsilon = 1e-10);
    }

    #[test]
    fn test_mass_flow_display() {
        let m = MassFlow::from_kg_per_s(0.1);
        assert_eq!(format!("{}", m), "0.1 kg/s");
    }

    #[test]
    fn test_mass_flow_arithmetic() {
        let m1 = MassFlow::from_kg_per_s(1.0);
        let m2 = MassFlow::from_kg_per_s(0.5);
        let m3 = m1 + m2;
        assert_relative_eq!(m3.to_kg_per_s(), 1.5, epsilon = 1e-10);

        let m4 = m1 - m2;
        assert_relative_eq!(m4.to_kg_per_s(), 0.5, epsilon = 1e-10);

        let m5 = m1 * 2.0;
        assert_relative_eq!(m5.to_kg_per_s(), 2.0, epsilon = 1e-10);

        let m6 = m1 / 2.0;
        assert_relative_eq!(m6.to_kg_per_s(), 0.5, epsilon = 1e-10);

        // Test reverse multiplication
        let m7 = 2.0 * m1;
        assert_relative_eq!(m7.to_kg_per_s(), 2.0, epsilon = 1e-10);
    }

    #[test]
    fn test_mass_flow_unit_conversions() {
        let m1 = MassFlow::from_grams_per_s(500.0);
        assert_relative_eq!(m1.to_kg_per_s(), 0.5, epsilon = 1e-6);
        assert_relative_eq!(m1.to_grams_per_s(), 500.0, epsilon = 1e-6);
    }

    #[test]
    fn test_mass_flow_regularized() {
        use super::MIN_MASS_FLOW_REGULARIZATION_KG_S;
        let zero = MassFlow::from_kg_per_s(0.0);
        let r = zero.regularized();
        assert_relative_eq!(
            r.to_kg_per_s(),
            MIN_MASS_FLOW_REGULARIZATION_KG_S,
            epsilon = 1e-15
        );
        let small = MassFlow::from_kg_per_s(1e-14);
        let r2 = small.regularized();
        assert_relative_eq!(
            r2.to_kg_per_s(),
            MIN_MASS_FLOW_REGULARIZATION_KG_S,
            epsilon = 1e-15
        );
        let normal = MassFlow::from_kg_per_s(0.5);
        let r3 = normal.regularized();
        assert_relative_eq!(r3.to_kg_per_s(), 0.5, epsilon = 1e-10);
    }

    // ==================== TYPE SAFETY TESTS ====================

    #[test]
    fn test_pressure_not_equal_to_temperature() {
        // This test ensures Pressure and Temperature are distinct types
        let p = Pressure::from_pascals(100_000.0);
        let t = Temperature::from_kelvin(100.0);

        // They should have different internal representations if units differ
        // But more importantly, this test would fail at compile time if we tried:
        // let x: Pressure = t; // Compile error!
        assert_ne!(p.0, t.0);
    }

    #[test]
    fn test_clone_and_copy() {
        let p1 = Pressure::from_pascals(100_000.0);
        let p2 = p1; // Copy
        let p3 = p1; // Copy (Copy trait is tested)

        assert_relative_eq!(p1.to_pascals(), p2.to_pascals(), epsilon = 1e-10);
        assert_relative_eq!(p1.to_pascals(), p3.to_pascals(), epsilon = 1e-10);
    }

    #[test]
    fn test_partial_ord() {
        let p1 = Pressure::from_pascals(100_000.0);
        let p2 = Pressure::from_pascals(200_000.0);

        assert!(p1 < p2);
        assert!(p2 > p1);
        assert!(p1 == Pressure::from_pascals(100_000.0));
    }

    #[test]
    fn test_partial_ord_with_nan() {
        let p1 = Pressure::from_pascals(100_000.0);
        let p_nan = Pressure(f64::NAN);

        // NaN comparisons should return false for all ordering operators
        assert_eq!(p_nan.partial_cmp(&p1), None);
        assert_eq!(p1.partial_cmp(&p_nan), None);
        assert_ne!(p_nan, p1);
        assert_ne!(p1, p_nan);

        // NaN should not equal itself
        assert_ne!(p_nan, p_nan);
    }

    // ==================== EDGE CASES ====================

    #[test]
    fn test_zero_values() {
        let p = Pressure::from_pascals(0.0);
        assert_relative_eq!(p.to_pascals(), 0.0, epsilon = 1e-10);
        assert_relative_eq!(p.to_bar(), 0.0, epsilon = 1e-10);

        let t = Temperature::from_kelvin(0.0);
        assert_relative_eq!(t.to_kelvin(), 0.0, epsilon = 1e-10);
        assert_relative_eq!(t.to_celsius(), -273.15, epsilon = 1e-10);
    }

    #[test]
    fn test_negative_values() {
        // Negative pressure doesn't make physical sense but is allowed by the type
        let p = Pressure::from_pascals(-1000.0);
        assert_relative_eq!(p.to_pascals(), -1000.0, epsilon = 1e-10);

        // Negative temperature (below absolute zero) doesn't make sense
        let t = Temperature::from_kelvin(-1.0);
        assert_relative_eq!(t.to_kelvin(), -1.0, epsilon = 1e-10);
    }

    #[test]
    fn test_very_large_values() {
        let p = Pressure::from_pascals(1e15);
        assert_relative_eq!(p.to_pascals(), 1e15, epsilon = 1e5);

        let t = Temperature::from_kelvin(1e9);
        assert_relative_eq!(t.to_kelvin(), 1e9, epsilon = 1e-1);
    }

    #[test]
    fn test_very_small_values() {
        let p = Pressure::from_pascals(1e-10);
        assert_relative_eq!(p.to_pascals(), 1e-10, epsilon = 1e-15);

        let m = MassFlow::from_kg_per_s(1e-12);
        assert_relative_eq!(m.to_kg_per_s(), 1e-12, epsilon = 1e-17);
    }

    // ==================== POWER TESTS ====================

    #[test]
    fn test_power_from_watts() {
        let p = Power::from_watts(1000.0);
        assert_relative_eq!(p.0, 1000.0, epsilon = 1e-10);
        assert_relative_eq!(p.to_watts(), 1000.0, epsilon = 1e-10);
    }

    #[test]
    fn test_power_from_kilowatts() {
        let p = Power::from_kilowatts(1.0);
        assert_relative_eq!(p.to_watts(), 1000.0, epsilon = 1e-6);
        assert_relative_eq!(p.to_kilowatts(), 1.0, epsilon = 1e-6);
    }

    #[test]
    fn test_power_from_megawatts() {
        let p = Power::from_megawatts(1.0);
        assert_relative_eq!(p.to_watts(), 1_000_000.0, epsilon = 1e-6);
        assert_relative_eq!(p.to_megawatts(), 1.0, epsilon = 1e-6);
    }

    #[test]
    fn test_power_display() {
        let p = Power::from_watts(5000.0);
        assert_eq!(format!("{}", p), "5000 W");
    }

    #[test]
    fn test_power_arithmetic() {
        let p1 = Power::from_watts(1000.0);
        let p2 = Power::from_watts(500.0);
        let p3 = p1 + p2;
        assert_relative_eq!(p3.to_watts(), 1500.0, epsilon = 1e-10);

        let p4 = p1 - p2;
        assert_relative_eq!(p4.to_watts(), 500.0, epsilon = 1e-10);

        let p5 = p1 * 2.0;
        assert_relative_eq!(p5.to_watts(), 2000.0, epsilon = 1e-10);

        let p6 = p1 / 2.0;
        assert_relative_eq!(p6.to_watts(), 500.0, epsilon = 1e-10);

        let p7 = 2.0 * p1;
        assert_relative_eq!(p7.to_watts(), 2000.0, epsilon = 1e-10);
    }

    // ==================== CIRCUIT ID TESTS ====================

    #[test]
    fn test_circuit_id_from_number() {
        let id = CircuitId::from_number(5);
        assert_eq!(id.as_number(), 5);
    }

    #[test]
    fn test_circuit_id_from_u16() {
        let id: CircuitId = 42u16.into();
        assert_eq!(id.0, 42);
    }

    #[test]
    fn test_circuit_id_from_u8() {
        let id: CircuitId = 42u8.into();
        assert_eq!(id.0, 42);
    }

    #[test]
    fn test_circuit_id_from_str_deterministic() {
        let id1: CircuitId = "primary".into();
        let id2: CircuitId = "primary".into();
        assert_eq!(id1, id2);
    }

    #[test]
    fn test_circuit_id_from_string() {
        let id = CircuitId::from("secondary".to_string());
        let id2: CircuitId = "secondary".into();
        assert_eq!(id, id2);
    }

    #[test]
    fn test_circuit_id_display() {
        let id = CircuitId(3);
        assert_eq!(format!("{}", id), "Circuit-3");
    }

    #[test]
    fn test_circuit_id_default() {
        let id = CircuitId::default();
        assert_eq!(id, CircuitId::ZERO);
        assert_eq!(id.0, 0);
    }

    #[test]
    fn test_circuit_id_zero_constant() {
        assert_eq!(CircuitId::ZERO.0, 0);
    }

    #[test]
    fn test_circuit_id_ordering() {
        let id1 = CircuitId(1);
        let id2 = CircuitId(2);
        assert!(id1 < id2);
        assert!(id2 > id1);
    }

    #[test]
    fn test_circuit_id_equality() {
        let id1 = CircuitId(5);
        let id2 = CircuitId(5);
        let id3 = CircuitId(6);
        assert_eq!(id1, id2);
        assert_ne!(id1, id3);
    }

    #[test]
    fn test_circuit_id_copy() {
        let id1 = CircuitId(10);
        let id2 = id1;
        assert_eq!(id1, id2);
    }

    #[test]
    fn test_circuit_id_hash_consistency() {
        use std::collections::HashSet;
        let mut set = HashSet::new();
        let id1: CircuitId = "circuit_a".into();
        let id2: CircuitId = "circuit_a".into();
        let id3: CircuitId = "circuit_b".into();
        set.insert(id1);
        assert!(set.contains(&id2));
        assert!(!set.contains(&id3));
    }
}
