//! Phase-change ε-NTU helpers (Shah / Incropera \(C^*\to 0\)).
//!
//! For condensers and evaporators where the refrigerant changes phase at
//! (ideally) constant \(T_{\mathrm{sat}}(P)\), the refrigerant heat-capacity
//! rate approaches infinity. Then \(C_{\min}=C_{\mathrm{sec}}\) and
//! \(\varepsilon = 1 - \exp(-\mathrm{UA}/C_{\mathrm{sec}})\).
//!
//! See Shah & Sekulić, *Fundamentals of Heat Exchanger Design* (Wiley 2003), §3.3.2.

/// Effectiveness for a single-stream (phase-change) exchanger:
/// \(\varepsilon = 1 - \exp(-\mathrm{UA}/C_{\mathrm{sec}})\).
#[inline]
pub fn phase_change_effectiveness(ua: f64, c_sec: f64) -> f64 {
    if c_sec <= 1e-10 || ua <= 0.0 {
        return 0.0;
    }
    1.0 - (-ua / c_sec).exp()
}

/// \(g'(C)\) where \(g(C)=C\cdot\varepsilon(C)=C\cdot(1-e^{-\mathrm{UA}/C})\).
///
/// Used for \(\partial Q/\partial\dot{m}_{\mathrm{sec}}\).
#[inline]
pub fn d_eps_c_d_c(ua: f64, c_sec: f64) -> f64 {
    if c_sec <= 1e-10 || ua <= 0.0 {
        return 0.0;
    }
    let e = (-ua / c_sec).exp();
    (1.0 - e) - (ua / c_sec) * e
}

/// Condenser duty [W]: \(Q = \varepsilon\,C_{\mathrm{sec}}\,(T_{\mathrm{sat}}-T_{\mathrm{sec,in}})\).
///
/// Positive \(Q\) = heat rejected by the refrigerant into the secondary.
#[inline]
pub fn condenser_duty(ua: f64, c_sec: f64, t_sat_k: f64, t_sec_in_k: f64) -> f64 {
    let eps = phase_change_effectiveness(ua, c_sec);
    eps * c_sec * (t_sat_k - t_sec_in_k)
}

/// Evaporator duty [W]: \(Q = \varepsilon\,C_{\mathrm{sec}}\,(T_{\mathrm{sec,in}}-T_{\mathrm{sat}})\).
///
/// Positive \(Q\) = heat absorbed by the refrigerant from the secondary.
#[inline]
pub fn evaporator_duty(ua: f64, c_sec: f64, t_sat_k: f64, t_sec_in_k: f64) -> f64 {
    let eps = phase_change_effectiveness(ua, c_sec);
    eps * c_sec * (t_sec_in_k - t_sat_k)
}

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

    #[test]
    fn effectiveness_cr_zero_matches_textbook() {
        let ua = 2500.0;
        let c_sec = 0.4 * 4180.0;
        let eps = phase_change_effectiveness(ua, c_sec);
        let expected = 1.0 - (-ua / c_sec).exp();
        assert!((eps - expected).abs() < 1e-12);
        assert!(eps > 0.7 && eps < 1.0);
    }

    #[test]
    fn condenser_duty_zero_when_tsat_equals_tsec() {
        let q = condenser_duty(5000.0, 1000.0, 303.15, 303.15);
        assert!(q.abs() < 1e-9);
    }

    #[test]
    fn evaporator_duty_positive_when_water_warmer_than_tsat() {
        let q = evaporator_duty(2000.0, 2000.0, 278.15, 285.15);
        assert!(q > 0.0);
    }

    #[test]
    fn d_eps_c_matches_fd() {
        let ua = 3000.0;
        let c = 1500.0;
        let h = 1e-3 * c;
        let gp = (c + h) * phase_change_effectiveness(ua, c + h);
        let gm = (c - h) * phase_change_effectiveness(ua, c - h);
        let fd = (gp - gm) / (2.0 * h);
        let an = d_eps_c_d_c(ua, c);
        assert!((an - fd).abs() / fd.abs().max(1e-9) < 1e-5);
    }
}
