//! Nucleate pool-boiling correlations for flooded evaporators.
//!
//! Implements Cooper (1984) and Mostinski (1963) for shell-side boiling on
//! tubes, plus Palen bundle and Thome–Robinson oil attenuation factors.

use super::correlation_registry::{
    assess_candidate, correlation_metadata, CandidateAssessment, CorrelationId,
    CorrelationMetadata, CorrelationPurpose, DomainInputError, ExchangerGeometryType, FlowRegime,
    OperatingPoint, SelectionContext,
};

/// Inputs for pool-boiling heat-transfer evaluation (SI).
#[derive(Debug, Clone, Copy)]
pub struct PoolBoilingInput {
    /// Reduced pressure `p_sat / p_crit` [-].
    pub p_reduced: f64,
    /// Heat flux [W/m²].
    pub heat_flux: f64,
    /// Molar mass [g/mol] (Cooper uses g/mol in its published form).
    pub molar_mass_g_mol: f64,
    /// Surface roughness Ra [μm]. Default 1.0 μm if unknown.
    pub roughness_um: f64,
    /// Critical pressure [Pa] (Mostinski).
    pub p_crit_pa: f64,
}

impl Default for PoolBoilingInput {
    fn default() -> Self {
        Self {
            p_reduced: 0.15,
            heat_flux: 15_000.0,
            molar_mass_g_mol: 102.0,
            roughness_um: 1.0,
            p_crit_pa: 4.059e6,
        }
    }
}

impl PoolBoilingInput {
    fn validate(&self) -> Result<(), DomainInputError> {
        for (field, value) in [
            ("p_reduced", self.p_reduced),
            ("heat_flux", self.heat_flux),
            ("molar_mass_g_mol", self.molar_mass_g_mol),
            ("roughness_um", self.roughness_um),
            ("p_crit_pa", self.p_crit_pa),
        ] {
            if !value.is_finite() || value <= 0.0 {
                return Err(DomainInputError::InvalidPositive { field, value });
            }
        }
        if self.p_reduced >= 1.0 {
            return Err(DomainInputError::InvalidPositive {
                field: "p_reduced",
                value: self.p_reduced,
            });
        }
        Ok(())
    }
}

/// Cooper (1984) nucleate pool-boiling HTC [W/(m²·K)].
///
/// `h = 55 · p_r^0.12 · (−log10 p_r)^(−0.55) · M^(−0.5) · q^0.67`
/// with roughness correction `−0.4343·ln(Ra)` on the `p_r^0.12` exponent term
/// as commonly implemented for industrial flooded evaporators.
pub fn cooper_1984(input: &PoolBoilingInput) -> Result<f64, DomainInputError> {
    input.validate()?;
    let pr = input.p_reduced.clamp(1e-6, 0.99);
    let ra = input.roughness_um.max(0.01);
    let log_term = (-pr.log10()).max(1e-6);
    let h = 55.0
        * pr.powf(0.12 - 0.4343 * ra.ln())
        * log_term.powf(-0.55)
        * input.molar_mass_g_mol.powf(-0.5)
        * input.heat_flux.powf(0.67);
    Ok(h.max(0.0))
}

/// Mostinski (1963) nucleate pool-boiling HTC [W/(m²·K)].
///
/// `h = 0.00417 · q^0.7 · p_crit^0.69 · F(p_r)` with
/// `F = 1.8 p_r^0.17 + 4 p_r^1.2 + 10 p_r^10` and `p_crit` in kN/m² (kPa).
pub fn mostinski_1963(input: &PoolBoilingInput) -> Result<f64, DomainInputError> {
    input.validate()?;
    let pr = input.p_reduced.clamp(1e-6, 0.99);
    let p_crit_kpa = input.p_crit_pa / 1.0e3;
    let f_pr = 1.8 * pr.powf(0.17) + 4.0 * pr.powf(1.2) + 10.0 * pr.powi(10);
    let h = 0.00417 * input.heat_flux.powf(0.7) * p_crit_kpa.powf(0.69) * f_pr;
    Ok(h.max(0.0))
}

/// Palen bundle correction factor (typical flooded evaporator range 0.6–0.9).
#[inline]
pub fn palen_bundle_factor(f_b: f64) -> f64 {
    f_b.clamp(0.5, 1.2)
}

/// Thome–Robinson (ASHRAE RP-1089) oil attenuation on pool-boiling HTC.
///
/// Linear blend from 1.0 at zero oil to ~0.25 at 10 % oil mass fraction
/// (published Turbo-BII bundle data). Clamped for numerical safety.
pub fn thome_robinson_oil_factor(oil_mass_fraction: f64) -> f64 {
    let w = oil_mass_fraction.clamp(0.0, 0.20);
    (1.0 - 7.5 * w).clamp(0.25, 1.0)
}

/// Combined flooded-shell HTC: `h_nb · F_b · F_oil` [W/(m²·K)].
pub fn flooded_shell_htc(
    input: &PoolBoilingInput,
    correlation: CorrelationId,
    bundle_factor: f64,
    oil_mass_fraction: f64,
) -> Result<f64, DomainInputError> {
    let h_nb = match correlation {
        CorrelationId::Cooper1984 => cooper_1984(input)?,
        CorrelationId::Mostinski1963 => mostinski_1963(input)?,
        other => {
            return Err(DomainInputError::InvalidPositive {
                field: "pool_boiling_correlation",
                value: other as u8 as f64,
            });
        }
    };
    Ok(h_nb * palen_bundle_factor(bundle_factor) * thome_robinson_oil_factor(oil_mass_fraction))
}

/// UA from refrigerant-side HTC and secondary-side HTC [W/K].
///
/// `1/UA = 1/(h_ref·A_ref) + 1/(h_sec·A_sec)` (wall resistance neglected).
pub fn ua_from_two_side_htc(h_ref: f64, area_ref_m2: f64, h_sec: f64, area_sec_m2: f64) -> f64 {
    let r_ref = 1.0 / (h_ref.max(1.0) * area_ref_m2.max(1e-9));
    let r_sec = 1.0 / (h_sec.max(1.0) * area_sec_m2.max(1e-9));
    1.0 / (r_ref + r_sec)
}

/// Returns Cooper registry metadata.
pub fn cooper_metadata() -> CorrelationMetadata {
    correlation_metadata(CorrelationId::Cooper1984)
}

/// Assesses Cooper applicability without evaluating the formula.
pub fn assess_cooper_domain(
    geometry: ExchangerGeometryType,
    refrigerant: Option<&str>,
) -> Result<CandidateAssessment, DomainInputError> {
    let context = SelectionContext {
        purpose: CorrelationPurpose::HeatTransfer,
        geometry,
        regime: FlowRegime::Evaporation,
        refrigerant: refrigerant.map(str::to_owned),
        operating_point: OperatingPoint {
            quality: Some(0.5),
            ..OperatingPoint::default()
        },
    };
    assess_candidate(&cooper_metadata(), &context)
}

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

    fn r134a_like() -> PoolBoilingInput {
        PoolBoilingInput {
            p_reduced: 0.12,
            heat_flux: 20_000.0,
            molar_mass_g_mol: 102.03,
            roughness_um: 1.0,
            p_crit_pa: 4.059e6,
        }
    }

    #[test]
    fn cooper_htc_plausible_magnitude() {
        let h = cooper_1984(&r134a_like()).unwrap();
        assert!(h.is_finite() && h > 500.0 && h < 50_000.0, "h={h}");
    }

    #[test]
    fn cooper_increases_with_heat_flux() {
        let mut low = r134a_like();
        low.heat_flux = 10_000.0;
        let mut high = r134a_like();
        high.heat_flux = 30_000.0;
        assert!(cooper_1984(&high).unwrap() > cooper_1984(&low).unwrap());
    }

    #[test]
    fn mostinski_positive() {
        let h = mostinski_1963(&r134a_like()).unwrap();
        assert!(h.is_finite() && h > 100.0);
    }

    #[test]
    fn oil_factor_degrades_htc() {
        assert!((thome_robinson_oil_factor(0.0) - 1.0).abs() < 1e-12);
        assert!(thome_robinson_oil_factor(0.10) < 0.35);
    }

    #[test]
    fn flooded_shell_applies_bundle_and_oil() {
        let base = cooper_1984(&r134a_like()).unwrap();
        let combined =
            flooded_shell_htc(&r134a_like(), CorrelationId::Cooper1984, 0.8, 0.05).unwrap();
        assert!(combined < base * 0.8);
        assert!(combined > 0.0);
    }

    #[test]
    fn ua_two_side_finite() {
        let ua = ua_from_two_side_htc(3000.0, 10.0, 5000.0, 8.0);
        assert!(ua.is_finite() && ua > 0.0);
    }

    #[test]
    fn cooper_rejects_invalid_p_reduced() {
        let mut inp = r134a_like();
        inp.p_reduced = 1.5;
        assert!(cooper_1984(&inp).is_err());
    }
}
