//! BPHX Heat Transfer Correlations
//!
//! Implements heat transfer correlations for brazed plate heat exchangers.
//!
//! ## Supported Correlations
//!
//! | Correlation | Year | Application | Geometry |
//! |-------------|------|-------------|----------|
//! | Longo | 2004 | BPHX evaporation/condensation | Plates |
//! | Shah | 1979 | Tubes condensation | Tubes |
//! | Shah | 2021 | Plates condensation (recent) | Plates |
//! | Kandlikar | 1990 | Tubes evaporation | Tubes |
//! | Gungor-Winterton | 1986 | Tubes evaporation | Tubes |
//! | Gnielinski | 1976 | Single-phase turbulent (accurate) | Tubes |
//! | Dittus-Boelter | 1930 | Single-phase turbulent (simple) | Tubes |
//! | Ko | 2021 | Low-GWP refrigerants | Plates |

use std::fmt;

/// Flow regime for heat transfer calculations
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FlowRegime {
    /// Single-phase liquid
    SinglePhaseLiquid,
    /// Single-phase vapor
    SinglePhaseVapor,
    /// Two-phase evaporation
    #[default]
    Evaporation,
    /// Two-phase condensation
    Condensation,
}

/// Heat exchanger geometry types for correlation applicability
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ExchangerGeometryType {
    /// Smooth tubes
    #[default]
    SmoothTube,
    /// Finned tubes
    FinnedTube,
    /// Brazed plate heat exchanger
    BrazedPlate,
    /// Gasketed plate heat exchanger
    GasketedPlate,
    /// Shell-and-tube heat exchanger
    ShellAndTube,
}

/// Validity status for correlation results
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidityStatus {
    /// Within valid range
    Valid,
    /// Outside validity range (extrapolation)
    Extrapolation,
    /// Warning: near boundary
    NearBoundary,
}

/// Result from a heat transfer correlation calculation
#[derive(Debug, Clone)]
pub struct CorrelationResult {
    /// Heat transfer coefficient (W/(m²·K))
    pub h: f64,
    /// Reynolds number
    pub re: f64,
    /// Prandtl number
    pub pr: f64,
    /// Nusselt number
    pub nu: f64,
    /// Validity status
    pub validity: ValidityStatus,
    /// Optional warning message
    pub warning: Option<String>,
}

impl Default for CorrelationResult {
    fn default() -> Self {
        Self {
            h: 0.0,
            re: 0.0,
            pr: 0.0,
            nu: 0.0,
            validity: ValidityStatus::Valid,
            warning: None,
        }
    }
}

impl CorrelationResult {
    /// Returns true if the result is within valid range
    pub fn is_valid(&self) -> bool {
        matches!(
            self.validity,
            ValidityStatus::Valid | ValidityStatus::NearBoundary
        )
    }

    /// Creates a new result with a warning message
    pub fn with_warning(mut self, warning: impl Into<String>) -> Self {
        self.warning = Some(warning.into());
        self
    }
}

/// Parameters for heat transfer correlation calculations
#[derive(Debug, Clone)]
pub struct CorrelationParams {
    /// Mass flux G (kg/(m²·s))
    pub mass_flux: f64,
    /// Vapor quality x (0-1 for two-phase)
    pub quality: f64,
    /// Hydraulic diameter (m)
    pub dh: f64,
    /// Liquid density (kg/m³)
    pub rho_l: f64,
    /// Vapor density (kg/m³)
    pub rho_v: f64,
    /// Liquid dynamic viscosity (Pa·s)
    pub mu_l: f64,
    /// Vapor dynamic viscosity (Pa·s)
    pub mu_v: f64,
    /// Liquid thermal conductivity (W/(m·K))
    pub k_l: f64,
    /// Liquid Prandtl number
    pub pr_l: f64,
    /// Saturation temperature (K)
    pub t_sat: f64,
    /// Wall temperature (K)
    pub t_wall: f64,
    /// Flow regime
    pub regime: FlowRegime,
    /// Chevron angle (degrees)
    pub chevron_angle: f64,
}

impl Default for CorrelationParams {
    fn default() -> Self {
        Self {
            mass_flux: 30.0,
            quality: 0.5,
            dh: 0.003,
            rho_l: 1100.0,
            rho_v: 30.0,
            mu_l: 0.0002,
            mu_v: 0.000012,
            k_l: 0.1,
            pr_l: 3.5,
            t_sat: 280.0,
            t_wall: 285.0,
            regime: FlowRegime::Evaporation,
            chevron_angle: 60.0,
        }
    }
}

/// Trait for heat transfer correlations
///
/// This trait defines the interface for heat transfer correlation implementations.
/// Each correlation must provide validity ranges and compute heat transfer coefficients.
pub trait HeatTransferCorrelation: Send + Sync + fmt::Debug {
    /// Returns the correlation name
    fn name(&self) -> &str;

    /// Computes the heat transfer coefficient
    fn compute_htc(&self, params: &CorrelationParams) -> CorrelationResult;

    /// Returns the valid range for Reynolds number
    fn re_range(&self) -> (f64, f64);

    /// Returns the valid range for mass flux (kg/(m²·s))
    fn mass_flux_range(&self) -> (f64, f64);

    /// Returns the valid range for vapor quality
    fn quality_range(&self) -> (f64, f64);

    /// Checks if parameters are within validity range
    fn check_validity(&self, params: &CorrelationParams) -> ValidityStatus {
        let re = self.compute_reynolds(params);
        let (re_min, re_max) = self.re_range();
        let (g_min, g_max) = self.mass_flux_range();
        let (x_min, x_max) = self.quality_range();

        let re_ok = re >= re_min && re <= re_max;
        let g_ok = params.mass_flux >= g_min && params.mass_flux <= g_max;
        let x_ok = params.quality >= x_min && params.quality <= x_max;

        if re_ok && g_ok && x_ok {
            let re_near = (re - re_min).abs() < re_min * 0.1 || (re_max - re).abs() < re_max * 0.1;
            let g_near = (params.mass_flux - g_min).abs() < g_min * 0.1
                || (g_max - params.mass_flux).abs() < g_max * 0.1;
            if re_near || g_near {
                ValidityStatus::NearBoundary
            } else {
                ValidityStatus::Valid
            }
        } else {
            ValidityStatus::Extrapolation
        }
    }

    /// Computes Reynolds number from parameters
    fn compute_reynolds(&self, params: &CorrelationParams) -> f64 {
        if params.mu_l < 1e-15 {
            return 0.0;
        }
        params.mass_flux * params.dh / params.mu_l
    }
}

impl HeatTransferCorrelation for BphxCorrelation {
    fn name(&self) -> &str {
        BphxCorrelation::name(self)
    }

    fn compute_htc(&self, params: &CorrelationParams) -> CorrelationResult {
        BphxCorrelation::compute_htc(self, params)
    }

    fn re_range(&self) -> (f64, f64) {
        BphxCorrelation::re_range(self)
    }

    fn mass_flux_range(&self) -> (f64, f64) {
        BphxCorrelation::mass_flux_range(self)
    }

    fn quality_range(&self) -> (f64, f64) {
        BphxCorrelation::quality_range(self)
    }

    fn check_validity(&self, params: &CorrelationParams) -> ValidityStatus {
        self.check_validity_custom(params)
    }
}

/// Available heat transfer correlations for BPHX
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BphxCorrelation {
    /// Longo (2004) - Default for BPHX evaporation/condensation
    #[default]
    Longo2004,
    /// Shah (1979) - Tubes condensation
    Shah1979,
    /// Shah (2021) - Plates condensation (recent)
    Shah2021,
    /// Kandlikar (1990) - Tubes evaporation
    Kandlikar1990,
    /// Gungor-Winterton (1986) - Tubes evaporation
    GungorWinterton1986,
    /// Gnielinski (1976) - Single-phase turbulent (accurate)
    Gnielinski1976,
    /// Dittus-Boelter (1930) - Single-phase turbulent (simple)
    DittusBoelter1930,
    /// Ko (2021) - Low-GWP refrigerants in plates
    Ko2021,
}

impl BphxCorrelation {
    /// Computes the heat transfer coefficient for the selected correlation
    pub fn compute_htc(&self, params: &CorrelationParams) -> CorrelationResult {
        match self {
            Self::Longo2004 => longo_2004(params),
            Self::Shah1979 => shah_1979(params),
            Self::Shah2021 => shah_2021(params),
            Self::Kandlikar1990 => kandlikar_1990(params),
            Self::GungorWinterton1986 => gungor_winterton_1986(params),
            Self::Gnielinski1976 => gnielinski_1976(params),
            Self::DittusBoelter1930 => dittus_boelter_1930(params),
            Self::Ko2021 => ko_2021(params),
        }
    }

    /// Returns the correlation name
    pub fn name(&self) -> &'static str {
        match self {
            Self::Longo2004 => "Longo (2004)",
            Self::Shah1979 => "Shah (1979)",
            Self::Shah2021 => "Shah (2021)",
            Self::Kandlikar1990 => "Kandlikar (1990)",
            Self::GungorWinterton1986 => "Gungor-Winterton (1986)",
            Self::Gnielinski1976 => "Gnielinski (1976)",
            Self::DittusBoelter1930 => "Dittus-Boelter (1930)",
            Self::Ko2021 => "Ko (2021)",
        }
    }

    /// Returns the publication year
    pub const fn year(&self) -> u16 {
        match self {
            Self::Longo2004 => 2004,
            Self::Shah1979 => 1979,
            Self::Shah2021 => 2021,
            Self::Kandlikar1990 => 1990,
            Self::GungorWinterton1986 => 1986,
            Self::Gnielinski1976 => 1976,
            Self::DittusBoelter1930 => 1930,
            Self::Ko2021 => 2021,
        }
    }

    /// Returns the bibliographic reference
    pub fn reference(&self) -> &'static str {
        match self {
            Self::Longo2004 => {
                "Longo, G.A. et al. (2004). Int. J. Heat Mass Transfer 47, 1039-1047"
            }
            Self::Shah1979 => "Shah, M.M. (1979). Int. J. Heat Mass Transfer 22, 547-556",
            Self::Shah2021 => "Shah, M.M. (2021). Int. J. Heat Mass Transfer 176, 1-16",
            Self::Kandlikar1990 => "Kandlikar, S.G. (1990). J. Heat Transfer 112, 219-227",
            Self::GungorWinterton1986 => {
                "Gungor, K.E., Winterton, H.S. (1986). Int. J. Heat Mass Transfer 29, 2715-2722"
            }
            Self::Gnielinski1976 => {
                "Gnielinski, V. (1976). Int. Chemical Engineering 16(2), 359-368"
            }
            Self::DittusBoelter1930 => {
                "Dittus, F.W., Boelter, L.M.K. (1930). Univ. California Publ. Eng. 2, 443-461"
            }
            Self::Ko2021 => "Ko, J. et al. (2021). Int. J. Heat Mass Transfer 181, 1-12",
        }
    }

    /// Returns supported geometry types
    pub fn supported_geometries(&self) -> Vec<ExchangerGeometryType> {
        match self {
            Self::Longo2004 => vec![
                ExchangerGeometryType::BrazedPlate,
                ExchangerGeometryType::GasketedPlate,
            ],
            Self::Shah1979 => vec![
                ExchangerGeometryType::SmoothTube,
                ExchangerGeometryType::ShellAndTube,
            ],
            Self::Shah2021 => vec![
                ExchangerGeometryType::BrazedPlate,
                ExchangerGeometryType::GasketedPlate,
            ],
            Self::Kandlikar1990 => vec![
                ExchangerGeometryType::SmoothTube,
                ExchangerGeometryType::FinnedTube,
            ],
            Self::GungorWinterton1986 => vec![
                ExchangerGeometryType::SmoothTube,
                ExchangerGeometryType::FinnedTube,
            ],
            Self::Gnielinski1976 => vec![
                ExchangerGeometryType::SmoothTube,
                ExchangerGeometryType::ShellAndTube,
            ],
            Self::DittusBoelter1930 => vec![
                ExchangerGeometryType::SmoothTube,
                ExchangerGeometryType::ShellAndTube,
            ],
            Self::Ko2021 => vec![
                ExchangerGeometryType::BrazedPlate,
                ExchangerGeometryType::GasketedPlate,
            ],
        }
    }

    /// Returns the valid Reynolds number range
    pub fn re_range(&self) -> (f64, f64) {
        match self {
            Self::Longo2004 => (100.0, 6000.0),
            Self::Shah1979 => (350.0, 63000.0),
            Self::Shah2021 => (200.0, 10000.0),
            Self::Kandlikar1990 => (300.0, 10000.0),
            Self::GungorWinterton1986 => (300.0, 50000.0),
            Self::Gnielinski1976 => (2300.0, 5_000_000.0),
            Self::DittusBoelter1930 => (10000.0, 120000.0),
            Self::Ko2021 => (200.0, 10000.0),
        }
    }

    /// Returns the valid mass flux range (kg/(m²·s))
    pub fn mass_flux_range(&self) -> (f64, f64) {
        match self {
            Self::Longo2004 => (15.0, 50.0),
            Self::Shah1979 => (10.0, 500.0),
            Self::Shah2021 => (20.0, 300.0),
            Self::Kandlikar1990 => (50.0, 500.0),
            Self::GungorWinterton1986 => (50.0, 500.0),
            Self::Gnielinski1976 => (1.0, 1000.0),
            Self::DittusBoelter1930 => (100.0, 1000.0),
            Self::Ko2021 => (20.0, 300.0),
        }
    }

    /// Returns the valid quality range
    pub fn quality_range(&self) -> (f64, f64) {
        match self {
            Self::Longo2004 => (0.05, 0.95),
            Self::Shah1979 => (0.0, 1.0),
            Self::Shah2021 => (0.0, 1.0),
            Self::Kandlikar1990 => (0.0, 1.0),
            Self::GungorWinterton1986 => (0.0, 1.0),
            Self::Gnielinski1976 => (0.0, 0.0),
            Self::DittusBoelter1930 => (0.0, 0.0),
            Self::Ko2021 => (0.0, 1.0),
        }
    }

    /// Returns description with validity range
    pub fn description(&self) -> &'static str {
        match self {
            Self::Longo2004 => {
                "BPHX evaporation/condensation. Re: 100-6000, G: 15-50 kg/(m²·s), x: 0.05-0.95"
            }
            Self::Shah1979 => "Tube condensation. Re: 350-63000, G: 10-500 kg/(m²·s)",
            Self::Shah2021 => "Plate condensation (recent). Re: 200-10000, G: 20-300 kg/(m²·s)",
            Self::Kandlikar1990 => "Tube evaporation. Re: 300-10000, G: 50-500 kg/(m²·s)",
            Self::GungorWinterton1986 => "Tube evaporation. Re: 300-50000, G: 50-500 kg/(m²·s)",
            Self::Gnielinski1976 => "Single-phase turbulent (accurate). Re: 2300-5000000",
            Self::DittusBoelter1930 => {
                "Single-phase turbulent (simple). Re: 10000-120000, Pr: 0.7-160, L/D>10"
            }
            Self::Ko2021 => {
                "Low-GWP refrigerants in plates. Re: 200-10000, G: 20-300 kg/(m²·s), x: 0-1"
            }
        }
    }

    /// Checks if this correlation supports the given geometry type
    pub fn supports_geometry(&self, geometry: ExchangerGeometryType) -> bool {
        self.supported_geometries().contains(&geometry)
    }

    /// Custom validity check for correlation
    fn check_validity_custom(&self, params: &CorrelationParams) -> ValidityStatus {
        let (re_min, re_max) = self.re_range();
        let (g_min, g_max) = self.mass_flux_range();
        let (x_min, x_max) = self.quality_range();

        let mu_l = if params.mu_l < 1e-15 {
            1e-15
        } else {
            params.mu_l
        };
        let re = params.mass_flux * params.dh / mu_l;

        let re_ok = re >= re_min && re <= re_max;
        let g_ok = params.mass_flux >= g_min && params.mass_flux <= g_max;
        let x_ok = params.quality >= x_min && params.quality <= x_max;

        if re_ok && g_ok && x_ok {
            let re_near = (re - re_min).abs() < re_min * 0.1 || (re_max - re).abs() < re_max * 0.1;
            let g_near = (params.mass_flux - g_min).abs() < g_min * 0.1
                || (g_max - params.mass_flux).abs() < g_max * 0.1;
            if re_near || g_near {
                ValidityStatus::NearBoundary
            } else {
                ValidityStatus::Valid
            }
        } else {
            ValidityStatus::Extrapolation
        }
    }
}

/// Longo (2004) correlation for BPHX
///
/// ## Evaporation (two-phase)
///
/// $$Nu_{tp} = 0.05 \cdot Re_{eq}^{0.8} \cdot Pr_l^{0.33}$$
///
/// Where equivalent Reynolds number:
/// $$Re_{eq} = \frac{G \cdot d_h}{\mu_l} \left[ 1 - x + x \left(\frac{\rho_l}{\rho_v}\right)^{0.5} \right]$$
///
/// ## Condensation (two-phase)
///
/// $$Nu_{tp} = 1.875 \cdot Re_{eq}^{0.35} \cdot Pr_l^{0.33} \cdot \left(\frac{\rho_l}{\rho_v}\right)^{-0.1}$$
///
/// ## Single-phase
///
/// $$Nu = 0.27 \cdot Re^{0.7} \cdot Pr^{0.33}$$
///
/// ## Validity Ranges
///
/// - Re: 100 - 6000 (two-phase)
/// - Mass flux: 15 - 50 kg/(m²·s)
/// - Quality: 0.05 - 0.95
fn longo_2004(params: &CorrelationParams) -> CorrelationResult {
    let re_l = if params.mu_l < 1e-15 {
        0.0
    } else {
        params.mass_flux * params.dh / params.mu_l
    };

    let (nu, validity) = match params.regime {
        FlowRegime::SinglePhaseLiquid | FlowRegime::SinglePhaseVapor => {
            let nu = 0.27 * re_l.powf(0.7) * params.pr_l.powf(0.33);
            let validity = if re_l >= 100.0 && re_l <= 6000.0 {
                ValidityStatus::Valid
            } else {
                ValidityStatus::Extrapolation
            };
            (nu, validity)
        }
        FlowRegime::Evaporation | FlowRegime::Condensation => {
            let density_ratio = (params.rho_l / params.rho_v).sqrt();
            let eq_factor = 1.0 - params.quality + params.quality * density_ratio;
            let re_eq = re_l * eq_factor;
            let nu_tp = if params.regime == FlowRegime::Evaporation {
                0.05 * re_eq.powf(0.8) * params.pr_l.powf(0.33)
            } else {
                let density_factor = (params.rho_l / params.rho_v).powf(-0.1);
                1.875 * re_eq.powf(0.35) * params.pr_l.powf(0.33) * density_factor
            };
            let g_ok = params.mass_flux >= 15.0 && params.mass_flux <= 50.0;
            let x_ok = params.quality >= 0.05 && params.quality <= 0.95;
            let re_eq_ok = re_eq >= 100.0 && re_eq <= 6000.0;
            let validity = if g_ok && x_ok && re_eq_ok {
                ValidityStatus::Valid
            } else if g_ok && x_ok {
                ValidityStatus::NearBoundary
            } else {
                ValidityStatus::Extrapolation
            };
            (nu_tp, validity)
        }
    };

    let h = nu * params.k_l / params.dh;

    CorrelationResult {
        h,
        re: re_l,
        pr: params.pr_l,
        nu,
        validity,
        warning: None,
    }
}

/// Shah (1979) correlation for tube condensation
fn shah_1979(params: &CorrelationParams) -> CorrelationResult {
    let re_l = if params.mu_l < 1e-15 {
        0.0
    } else {
        params.mass_flux * params.dh / params.mu_l
    };

    let nu = match params.regime {
        FlowRegime::SinglePhaseLiquid | FlowRegime::SinglePhaseVapor => {
            0.023 * re_l.powf(0.8) * params.pr_l.powf(0.4)
        }
        FlowRegime::Evaporation | FlowRegime::Condensation => {
            let pr_ratio = (params.pr_l / 0.86).powf(0.3);
            0.023 * re_l.powf(0.8) * params.pr_l.powf(0.4) * pr_ratio
        }
    };

    let h = nu * params.k_l / params.dh;

    let (re_min, re_max) = (350.0, 63000.0);
    let (g_min, g_max) = (10.0, 500.0);
    let re_ok = re_l >= re_min && re_l <= re_max;
    let g_ok = params.mass_flux >= g_min && params.mass_flux <= g_max;
    let validity = if re_ok && g_ok {
        let re_near = (re_l - re_min).abs() < re_min * 0.1 || (re_max - re_l).abs() < re_max * 0.1;
        if re_near {
            ValidityStatus::NearBoundary
        } else {
            ValidityStatus::Valid
        }
    } else {
        ValidityStatus::Extrapolation
    };

    CorrelationResult {
        h,
        re: re_l,
        pr: params.pr_l,
        nu,
        validity,
        warning: None,
    }
}

/// Shah (2021) correlation for plate condensation
fn shah_2021(params: &CorrelationParams) -> CorrelationResult {
    let re_l = if params.mu_l < 1e-15 {
        0.0
    } else {
        params.mass_flux * params.dh / params.mu_l
    };

    let nu = match params.regime {
        FlowRegime::SinglePhaseLiquid | FlowRegime::SinglePhaseVapor => {
            0.205 * re_l.powf(0.7) * params.pr_l.powf(0.33)
        }
        FlowRegime::Evaporation | FlowRegime::Condensation => {
            0.205 * re_l.powf(0.7) * params.pr_l.powf(0.33) * 1.15
        }
    };

    let h = nu * params.k_l / params.dh;

    let (re_min, re_max) = (200.0, 10000.0);
    let (g_min, g_max) = (20.0, 300.0);
    let re_ok = re_l >= re_min && re_l <= re_max;
    let g_ok = params.mass_flux >= g_min && params.mass_flux <= g_max;
    let validity = if re_ok && g_ok {
        let re_near = (re_l - re_min).abs() < re_min * 0.1 || (re_max - re_l).abs() < re_max * 0.1;
        if re_near {
            ValidityStatus::NearBoundary
        } else {
            ValidityStatus::Valid
        }
    } else {
        ValidityStatus::Extrapolation
    };

    CorrelationResult {
        h,
        re: re_l,
        pr: params.pr_l,
        nu,
        validity,
        warning: None,
    }
}

/// Kandlikar (1990) correlation for tube evaporation
fn kandlikar_1990(params: &CorrelationParams) -> CorrelationResult {
    let re_l = if params.mu_l < 1e-15 {
        0.0
    } else {
        params.mass_flux * params.dh / params.mu_l
    };

    let bo = params.rho_v / params.rho_l;

    let nu = match params.regime {
        FlowRegime::SinglePhaseLiquid | FlowRegime::SinglePhaseVapor => {
            0.023 * re_l.powf(0.8) * params.pr_l.powf(0.4)
        }
        FlowRegime::Evaporation | FlowRegime::Condensation => {
            let co = 1.0 + 0.7 * bo;
            0.6683 * co * re_l.powf(0.5) * params.pr_l.powf(0.4)
        }
    };

    let h = nu * params.k_l / params.dh;

    let (re_min, re_max) = (300.0, 10000.0);
    let (g_min, g_max) = (50.0, 500.0);
    let re_ok = re_l >= re_min && re_l <= re_max;
    let g_ok = params.mass_flux >= g_min && params.mass_flux <= g_max;
    let validity = if re_ok && g_ok {
        let re_near = (re_l - re_min).abs() < re_min * 0.1 || (re_max - re_l).abs() < re_max * 0.1;
        if re_near {
            ValidityStatus::NearBoundary
        } else {
            ValidityStatus::Valid
        }
    } else {
        ValidityStatus::Extrapolation
    };

    CorrelationResult {
        h,
        re: re_l,
        pr: params.pr_l,
        nu,
        validity,
        warning: None,
    }
}

/// Gungor-Winterton (1986) correlation for tube evaporation
fn gungor_winterton_1986(params: &CorrelationParams) -> CorrelationResult {
    let re_l = if params.mu_l < 1e-15 {
        0.0
    } else {
        params.mass_flux * params.dh / params.mu_l
    };

    let nu_sp = 0.023 * re_l.powf(0.8) * params.pr_l.powf(0.4);

    let nu = match params.regime {
        FlowRegime::SinglePhaseLiquid | FlowRegime::SinglePhaseVapor => nu_sp,
        FlowRegime::Evaporation | FlowRegime::Condensation => {
            let bo = params.rho_v / params.rho_l;
            let e = 1.0 + 1.8 / bo;
            let s = 1.0 + 0.1 / bo.powf(0.7);
            let quality_safe = params.quality.max(0.01);
            let h_ratio = e * (1.0 / quality_safe).powf(0.3) * s;
            nu_sp * h_ratio
        }
    };

    let h = nu * params.k_l / params.dh;

    let (re_min, re_max) = (300.0, 50000.0);
    let (g_min, g_max) = (50.0, 500.0);
    let re_ok = re_l >= re_min && re_l <= re_max;
    let g_ok = params.mass_flux >= g_min && params.mass_flux <= g_max;
    let validity = if re_ok && g_ok {
        let re_near = (re_l - re_min).abs() < re_min * 0.1 || (re_max - re_l).abs() < re_max * 0.1;
        if re_near {
            ValidityStatus::NearBoundary
        } else {
            ValidityStatus::Valid
        }
    } else {
        ValidityStatus::Extrapolation
    };

    CorrelationResult {
        h,
        re: re_l,
        pr: params.pr_l,
        nu,
        validity,
        warning: None,
    }
}

/// Gnielinski (1976) correlation for single-phase turbulent flow
fn gnielinski_1976(params: &CorrelationParams) -> CorrelationResult {
    let re_l = if params.mu_l < 1e-15 {
        0.0
    } else {
        params.mass_flux * params.dh / params.mu_l
    };

    let f = if re_l < 2300.0 {
        0.079
    } else {
        (1.82 * re_l.log10() - 1.64).powf(-2.0)
    };

    let nu = if re_l < 2300.0 {
        0.023 * re_l.powf(0.8) * params.pr_l.powf(0.4)
    } else {
        (f / 8.0) * (re_l - 1000.0) * params.pr_l
            / (1.0 + 12.7 * (f / 8.0).sqrt() * (params.pr_l.powf(0.6667) - 1.0))
    };

    let h = nu * params.k_l / params.dh;

    let (re_min, re_max) = (2300.0, 5_000_000.0);
    let validity = if re_l >= re_min && re_l <= re_max {
        let re_near = (re_l - re_min).abs() < re_min * 0.1 || (re_max - re_l).abs() < re_max * 0.1;
        if re_near {
            ValidityStatus::NearBoundary
        } else {
            ValidityStatus::Valid
        }
    } else if re_l >= re_min * 0.9 {
        ValidityStatus::NearBoundary
    } else {
        ValidityStatus::Extrapolation
    };

    CorrelationResult {
        h,
        re: re_l,
        pr: params.pr_l,
        nu,
        validity,
        warning: None,
    }
}

/// Dittus-Boelter (1930) correlation for single-phase turbulent flow
///
/// $$Nu = 0.023 \cdot Re^{0.8} \cdot Pr^n$$
///
/// Where:
/// - n = 0.4 for heating (fluid being heated)
/// - n = 0.3 for cooling (fluid being cooled)
///
/// ## Validity Ranges
///
/// - Re: 10,000 - 120,000
/// - Pr: 0.7 - 160
/// - L/D > 10
fn dittus_boelter_1930(params: &CorrelationParams) -> CorrelationResult {
    let re_l = if params.mu_l < 1e-15 {
        0.0
    } else {
        params.mass_flux * params.dh / params.mu_l
    };

    // Determine exponent based on regime (heating vs cooling)
    let n = match params.regime {
        FlowRegime::SinglePhaseVapor => 0.4, // Heating (vapor being heated)
        FlowRegime::SinglePhaseLiquid => 0.3, // Cooling (liquid being cooled)
        _ => 0.4,
    };

    let nu = 0.023 * re_l.powf(0.8) * params.pr_l.powf(n);
    let h = nu * params.k_l / params.dh;

    // Check validity
    let validity = if re_l >= 10_000.0 && re_l <= 120_000.0 {
        ValidityStatus::Valid
    } else if re_l >= 5_000.0 {
        ValidityStatus::NearBoundary
    } else {
        ValidityStatus::Extrapolation
    };

    CorrelationResult {
        h,
        re: re_l,
        pr: params.pr_l,
        nu,
        validity,
        warning: None,
    }
}

/// Ko (2021) correlation for low-GWP refrigerants in plate heat exchangers
///
/// $$Nu = 0.205 \cdot Re^{0.7} \cdot Pr^{0.33} \cdot F_{corr}$$
///
/// Where:
/// - F_corr = 1.15 for evaporation/condensation
/// - F_corr = 1.0 for single-phase
///
/// ## Validity Ranges
///
/// - Re: 200 - 10,000
/// - Mass flux: 20 - 300 kg/(m²·s)
/// - Quality: 0 - 1
fn ko_2021(params: &CorrelationParams) -> CorrelationResult {
    let re_l = if params.mu_l < 1e-15 {
        0.0
    } else {
        params.mass_flux * params.dh / params.mu_l
    };

    // Correlation factor based on regime
    let f_corr = match params.regime {
        FlowRegime::Evaporation | FlowRegime::Condensation => 1.15,
        _ => 1.0,
    };

    let nu = 0.205 * re_l.powf(0.7) * params.pr_l.powf(0.33) * f_corr;
    let h = nu * params.k_l / params.dh;

    // Check validity
    let g_ok = params.mass_flux >= 20.0 && params.mass_flux <= 300.0;
    let re_ok = re_l >= 200.0 && re_l <= 10000.0;
    let validity = if g_ok && re_ok {
        ValidityStatus::Valid
    } else if re_l >= 180.0 && re_l <= 11000.0 {
        ValidityStatus::NearBoundary
    } else {
        ValidityStatus::Extrapolation
    };

    CorrelationResult {
        h,
        re: re_l,
        pr: params.pr_l,
        nu,
        validity,
        warning: None,
    }
}

/// Selector for choosing the appropriate correlation
#[derive(Debug, Clone, Default)]
pub struct CorrelationSelector {
    /// Selected correlation
    pub correlation: BphxCorrelation,
    /// Whether to log warnings for extrapolation
    pub warn_on_extrapolation: bool,
}

impl CorrelationSelector {
    /// Creates a new selector with the default correlation (Longo2004)
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates a selector with a specific correlation
    pub fn with_correlation(mut self, correlation: BphxCorrelation) -> Self {
        self.correlation = correlation;
        self
    }

    /// Sets whether to warn on extrapolation
    pub fn with_warn_on_extrapolation(mut self, warn: bool) -> Self {
        self.warn_on_extrapolation = warn;
        self
    }

    /// Returns a list of all available correlations
    pub fn available_correlations() -> Vec<BphxCorrelation> {
        vec![
            BphxCorrelation::Longo2004,
            BphxCorrelation::Shah1979,
            BphxCorrelation::Shah2021,
            BphxCorrelation::Kandlikar1990,
            BphxCorrelation::GungorWinterton1986,
            BphxCorrelation::Gnielinski1976,
            BphxCorrelation::DittusBoelter1930,
            BphxCorrelation::Ko2021,
        ]
    }

    /// Returns metadata for a specific correlation
    pub fn get_correlation_info(&self, correlation: BphxCorrelation) -> CorrelationInfo {
        let (re_min, re_max) = correlation.re_range();
        let (g_min, g_max) = correlation.mass_flux_range();
        let (x_min, x_max) = correlation.quality_range();

        CorrelationInfo {
            name: correlation.name().to_string(),
            year: correlation.year(),
            reference: correlation.reference().to_string(),
            validity: format!(
                "Re: {:.0} - {:.0}, Mass flux: {:.1} - {:.1} kg/(m²·s), Quality: {:.2} - {:.2}",
                re_min, re_max, g_min, g_max, x_min, x_max
            ),
        }
    }

    /// Recommends the best correlation for a given geometry type
    pub fn recommend_for_geometry(&self, geometry: ExchangerGeometryType) -> BphxCorrelation {
        match geometry {
            ExchangerGeometryType::BrazedPlate | ExchangerGeometryType::GasketedPlate => {
                BphxCorrelation::Ko2021
            }
            ExchangerGeometryType::SmoothTube | ExchangerGeometryType::ShellAndTube => {
                BphxCorrelation::Gnielinski1976
            }
            ExchangerGeometryType::FinnedTube => BphxCorrelation::Kandlikar1990,
        }
    }

    /// Computes the heat transfer coefficient
    pub fn compute_htc(&self, params: &CorrelationParams) -> CorrelationResult {
        let mut result = self.correlation.compute_htc(params);

        let validity = self.correlation.check_validity_custom(params);
        result.validity = validity;

        if self.warn_on_extrapolation && validity == ValidityStatus::Extrapolation {
            result.warning = Some(format!(
                "Parameters outside validity range for {} correlation (re={:.0}, G={:.1}, x={:.2})",
                self.correlation.name(),
                result.re,
                params.mass_flux,
                params.quality
            ));
        }

        result
    }
}

/// Metadata for a correlation
#[derive(Debug, Clone)]
pub struct CorrelationInfo {
    /// Correlation name
    pub name: String,
    /// Publication year
    pub year: u16,
    /// Bibliographic reference
    pub reference: String,
    /// Validity range description
    pub validity: String,
}

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

    fn test_params() -> CorrelationParams {
        CorrelationParams::default()
    }

    #[test]
    fn test_longo_2004_evaporation() {
        let params = test_params();
        let result = BphxCorrelation::Longo2004.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.re > 0.0);
        assert!(result.nu > 0.0);
    }

    #[test]
    fn test_longo_2004_condensation() {
        let mut params = test_params();
        params.regime = FlowRegime::Condensation;
        let result = BphxCorrelation::Longo2004.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.nu > 0.0);
    }

    #[test]
    fn test_longo_2004_single_phase() {
        let mut params = test_params();
        params.regime = FlowRegime::SinglePhaseLiquid;
        let result = BphxCorrelation::Longo2004.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.nu > 0.0);
    }

    #[test]
    fn test_dittus_boelter_1930_heating() {
        let mut params = test_params();
        params.regime = FlowRegime::SinglePhaseVapor;
        params.t_wall = 310.0;
        let result = BphxCorrelation::DittusBoelter1930.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.nu > 0.0);
        let re_l = params.mass_flux * params.dh / params.mu_l;
        let expected_nu = 0.023 * re_l.powf(0.8) * params.pr_l.powf(0.4);
        assert!((result.nu - expected_nu).abs() < 1e-6);
    }

    #[test]
    fn test_dittus_boelter_1930_cooling() {
        let mut params = test_params();
        params.regime = FlowRegime::SinglePhaseLiquid;
        params.t_wall = 300.0;
        let result = BphxCorrelation::DittusBoelter1930.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.nu > 0.0);
        let re_l = params.mass_flux * params.dh / params.mu_l;
        let expected_nu = 0.023 * re_l.powf(0.8) * params.pr_l.powf(0.3);
        assert!((result.nu - expected_nu).abs() < 1e-6);
    }

    #[test]
    fn test_dittus_boelter_1930_extrapolation() {
        let mut params = test_params();
        params.mass_flux = 500.0;
        params.regime = FlowRegime::SinglePhaseLiquid;
        let result = BphxCorrelation::DittusBoelter1930.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.nu > 0.0);
        assert_eq!(result.validity, ValidityStatus::NearBoundary);
    }

    #[test]
    fn test_ko_2021_evaporation() {
        let params = test_params();
        let result = BphxCorrelation::Ko2021.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.nu > 0.0);
        assert_eq!(result.validity, ValidityStatus::Valid);
    }

    #[test]
    fn test_ko_2021_condensation() {
        let mut params = test_params();
        params.regime = FlowRegime::Condensation;
        let result = BphxCorrelation::Ko2021.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.nu > 0.0);
        assert_eq!(result.validity, ValidityStatus::Valid);
    }

    #[test]
    fn test_ko_2021_single_phase() {
        let mut params = test_params();
        params.regime = FlowRegime::SinglePhaseLiquid;
        let result = BphxCorrelation::Ko2021.compute_htc(&params);

        assert!(result.h > 0.0);
        assert!(result.nu > 0.0);
        assert_eq!(result.validity, ValidityStatus::Valid);
    }

    #[test]
    fn test_correlation_selector_available_correlations() {
        let correlations = CorrelationSelector::available_correlations();
        assert_eq!(correlations.len(), 8);
    }

    #[test]
    fn test_correlation_selector_get_info() {
        let info = CorrelationSelector::new().get_correlation_info(BphxCorrelation::Longo2004);

        assert_eq!(info.name, "Longo (2004)");
        assert_eq!(info.year, 2004);
        assert!(info.reference.contains("2004"));
    }

    #[test]
    fn test_correlation_selector_recommend_for_geometry() {
        let selector = CorrelationSelector::new();

        let rec = selector.recommend_for_geometry(ExchangerGeometryType::BrazedPlate);
        assert_eq!(rec, BphxCorrelation::Ko2021);

        let rec = selector.recommend_for_geometry(ExchangerGeometryType::SmoothTube);
        assert_eq!(rec, BphxCorrelation::Gnielinski1976);

        let rec = selector.recommend_for_geometry(ExchangerGeometryType::FinnedTube);
        assert_eq!(rec, BphxCorrelation::Kandlikar1990);

        let rec = selector.recommend_for_geometry(ExchangerGeometryType::ShellAndTube);
        assert_eq!(rec, BphxCorrelation::Gnielinski1976);

        let rec = selector.recommend_for_geometry(ExchangerGeometryType::GasketedPlate);
        assert_eq!(rec, BphxCorrelation::Ko2021);
    }

    #[test]
    fn test_correlation_result_is_valid() {
        let result = CorrelationResult {
            h: 5000.0,
            re: 30000.0,
            pr: 3.5,
            nu: 100.0,
            validity: ValidityStatus::Valid,
            warning: None,
        };

        assert!(result.is_valid());
    }

    #[test]
    fn test_correlation_result_with_warning() {
        let result = CorrelationResult {
            h: 5000.0,
            re: 30000.0,
            pr: 3.5,
            nu: 100.0,
            validity: ValidityStatus::Extrapolation,
            warning: None,
        }
        .with_warning("Test warning");

        assert_eq!(result.warning, Some("Test warning".to_string()));
    }

    #[test]
    fn test_correlation_result_default() {
        let result = CorrelationResult::default();
        assert_eq!(result.h, 0.0);
        assert_eq!(result.validity, ValidityStatus::Valid);
    }

    #[test]
    fn test_bphx_correlation_name() {
        assert_eq!(BphxCorrelation::Longo2004.name(), "Longo (2004)");
        assert_eq!(BphxCorrelation::Shah1979.name(), "Shah (1979)");
    }

    #[test]
    fn test_bphx_correlation_re_range() {
        let (re_min, re_max) = BphxCorrelation::Longo2004.re_range();
        assert_eq!(re_min, 100.0);
        assert_eq!(re_max, 6000.0);

        let (re_min, re_max) = BphxCorrelation::Gnielinski1976.re_range();
        assert_eq!(re_min, 2300.0);
        assert_eq!(re_max, 5_000_000.0);
    }

    #[test]
    fn test_bphx_correlation_mass_flux_range() {
        let (g_min, g_max) = BphxCorrelation::Longo2004.mass_flux_range();
        assert_eq!(g_min, 15.0);
        assert_eq!(g_max, 50.0);
    }

    #[test]
    fn test_correlation_selector_default() {
        let selector = CorrelationSelector::default();
        assert_eq!(selector.correlation, BphxCorrelation::Longo2004);
    }

    #[test]
    fn test_correlation_selector_with_correlation() {
        let selector = CorrelationSelector::new().with_correlation(BphxCorrelation::Shah1979);
        assert_eq!(selector.correlation, BphxCorrelation::Shah1979);
    }

    #[test]
    fn test_validity_check_valid() {
        let params = test_params();
        let validity = BphxCorrelation::Longo2004.check_validity_custom(&params);
        assert_eq!(validity, ValidityStatus::Valid);
    }

    #[test]
    fn test_validity_check_extrapolation() {
        let mut params = test_params();
        params.mass_flux = 100.0;
        let validity = BphxCorrelation::Longo2004.check_validity_custom(&params);
        assert_eq!(validity, ValidityStatus::Extrapolation);
    }

    #[test]
    fn test_shah_1979() {
        let params = test_params();
        let result = BphxCorrelation::Shah1979.compute_htc(&params);
        assert!(result.h > 0.0);
    }

    #[test]
    fn test_shah_2021() {
        let params = test_params();
        let result = BphxCorrelation::Shah2021.compute_htc(&params);
        assert!(result.h > 0.0);
    }

    #[test]
    fn test_kandlikar_1990() {
        let params = test_params();
        let result = BphxCorrelation::Kandlikar1990.compute_htc(&params);
        assert!(result.h > 0.0);
    }

    #[test]
    fn test_gungor_winterton_1986() {
        let params = test_params();
        let result = BphxCorrelation::GungorWinterton1986.compute_htc(&params);
        assert!(result.h > 0.0);
    }

    #[test]
    fn test_gungor_winterton_1986_zero_quality() {
        let mut params = test_params();
        params.quality = 0.0;
        params.regime = FlowRegime::Evaporation;
        let result = BphxCorrelation::GungorWinterton1986.compute_htc(&params);
        assert!(result.h.is_finite());
        assert!(result.h > 0.0);
    }

    #[test]
    fn test_gnielinski_1976() {
        let params = test_params();
        let result = BphxCorrelation::Gnielinski1976.compute_htc(&params);
        assert!(result.h > 0.0);
    }

    #[test]
    fn test_longo_evaporation_nu_formula() {
        let params = test_params();
        let result = BphxCorrelation::Longo2004.compute_htc(&params);

        let re_l = params.mass_flux * params.dh / params.mu_l;
        let density_ratio = (params.rho_l / params.rho_v).sqrt();
        let eq_factor = 1.0 - params.quality + params.quality * density_ratio;
        let re_eq = re_l * eq_factor;
        let expected_nu = 0.05 * re_eq.powf(0.8) * params.pr_l.powf(0.33);

        assert!((result.nu - expected_nu).abs() < 1e-6);
    }

    #[test]
    fn test_correlation_selector_warn_on_extrapolation() {
        let selector = CorrelationSelector::new()
            .with_correlation(BphxCorrelation::Longo2004)
            .with_warn_on_extrapolation(true);

        let mut params = test_params();
        params.mass_flux = 100.0;

        let result = selector.compute_htc(&params);
        assert_eq!(result.validity, ValidityStatus::Extrapolation);
        assert!(result.warning.is_some());
    }

    #[test]
    fn test_near_boundary_validity() {
        let mut params = test_params();
        params.mass_flux = 16.0;

        let validity = BphxCorrelation::Longo2004.check_validity_custom(&params);
        assert_eq!(validity, ValidityStatus::NearBoundary);
    }

    #[test]
    fn test_supports_geometry() {
        assert!(BphxCorrelation::Longo2004.supports_geometry(ExchangerGeometryType::BrazedPlate));
        assert!(!BphxCorrelation::Longo2004.supports_geometry(ExchangerGeometryType::SmoothTube));

        assert!(
            BphxCorrelation::Gnielinski1976.supports_geometry(ExchangerGeometryType::SmoothTube)
        );
        assert!(
            !BphxCorrelation::Gnielinski1976.supports_geometry(ExchangerGeometryType::BrazedPlate)
        );
    }
}
