//! Fin-and-Tube Condenser Coil — RTPF (Round Tube Plate Fin)
//!
//! Modèle physique complet d'un condenseur à air à ailettes et tubes ronds,
//! tel qu'utilisé dans les chillers air-cooled industriels (Carrier, Trane, York, Daikin).
//!
//! ## Paramètres géométriques ingénieur
//!
//! ```text
//! ┌──────────────────────────────────────────────────┐
//! │           COIL FACE (vue de face)                │
//! │                                                  │
//! │  ←── face_width_m ──→                           │
//! │  ┌─────────────────────┐  ↑                     │
//! │  │  ○  ○  ○  ○  ○  ○  │  │ face_height_m       │
//! │  │  ○  ○  ○  ○  ○  ○  │  │                     │
//! │  │  ○  ○  ○  ○  ○  ○  │  ↓                     │
//! │  └─────────────────────┘                        │
//! │       ↑ Pt (tube pitch transversal)              │
//! └──────────────────────────────────────────────────┘
//!
//! Profondeur (coupe latérale) :
//!  ← n_rows × Pl →
//!  ○  ○  ○   (n_rows = 3, arrangement staggered)
//!     ○  ○
//!  ○  ○  ○
//! ```
//!
//! ## Physique — UA depuis la géométrie
//!
//! ### Côté air (dominant)
//!
//! Corrélation de Chang & Wang (1997) pour ailettes à persiennes (louvered) :
//! ```text
//! j = C × Re_Lp^n    (facteur de Colburn pour transfert de chaleur)
//! h_air = j × G_air × Cp_air / Pr^(2/3)
//! ```
//! Pour autres types d'ailettes : facteurs multiplicatifs empiriques.
//!
//! Efficacité des ailettes (profil rectangulaire) :
//! ```text
//! m = sqrt(2 × h_air / (k_fin × t_fin))
//! L = (Pt/2 - d_tube/2) × correction_stagger
//! η_fin = tanh(m × L) / (m × L)
//! η_surface = 1 - (1 - η_fin) × A_fin / A_total
//! UA_air = η_surface × h_air × A_total
//! ```
//!
//! ### Côté réfrigérant (condensation, résistance faible ~5-10%)
//!
//! ```text
//! UA_total = 1 / (1/UA_air + 1/UA_ref)  ≈ UA_air × 0.92  (résistance réfrigérant ≈ 8%)
//! ```
//!
//! ### Température de condensation — Méthode ε-NTU (isotherme côté réfrigérant)
//!
//! Pour condensation isotherme (T_cond = const), la méthode ε-NTU donne :
//! ```text
//! ṁ_air = ρ_air × A_face × v_face
//! C_air = ṁ_air × Cp_air
//! NTU = UA_total / C_air
//! ε = 1 - exp(-NTU)              (efficacité pour fluide isotherme)
//! ΔT_air = Q_design / C_air      (montée en température côté air)
//! T_cond = OAT + ΔT_air / ε      (EXACT pour condensation isotherme)
//! ```
//!
//! ## Références
//!
//! - Chang & Wang (1997) : Int. J. Heat Mass Transfer, 40(3):533–544
//! - ASHRAE Handbook of Fundamentals (2021), Chap. 4 — Two-Phase Flow
//! - Webb & Kim (2005) : Principles of Enhanced Heat Transfer

use super::condenser::Condenser;
use crate::state_machine::{CircuitId, OperationalState, StateManageable};
use crate::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_core::Calib;
use entropyk_fluids::FluidBackend;
use std::sync::Arc;

// ─────────────────────────────────────────────────────────────────────────────
// Constantes physiques air (conditions standard)
// ─────────────────────────────────────────────────────────────────────────────

/// Chaleur spécifique de l'air sec [J/(kg·K)]
const CP_AIR: f64 = 1006.0;
/// Nombre de Prandtl de l'air (~35°C)
const PR_AIR: f64 = 0.707;
/// Viscosité dynamique de l'air (~35°C) [Pa·s]
const MU_AIR: f64 = 1.87e-5;
/// Conductivité thermique de l'air (~35°C) [W/(m·K)]
#[allow(dead_code)] // Reference air property, kept for completeness/future correlations.
const K_AIR: f64 = 0.0270;
/// Conductivité des ailettes aluminium [W/(m·K)]
const K_FIN_ALU: f64 = 205.0;
/// Densité air standard (35°C, 101.325 kPa) [kg/m³]
#[allow(dead_code)] // Reference air property, kept for completeness/future correlations.
const RHO_AIR_STD: f64 = 1.145;
/// Fraction de résistance côté réfrigérant (condensation) — correction UA_total
const REF_RESISTANCE_FACTOR: f64 = 0.92;

// ─────────────────────────────────────────────────────────────────────────────
// Types publics
// ─────────────────────────────────────────────────────────────────────────────

/// Type d'ailette — détermine la corrélation de transfert de chaleur côté air.
///
/// | Type       | Corrélation     | Performance relative | Application typique         |
/// |------------|-----------------|----------------------|-----------------------------|
/// | Louvered   | Chang & Wang    | 100% (référence)     | Chillers air-cooled modernes|
/// | Wavy       | Corrélation VDI | 78%                  | Anciennes unités, plus robuste|
/// | Slit       | Interpolation   | 93%                  | Compromis performance/coût  |
/// | Plain      | Gnielinski-fin  | 58%                  | Ambiances corrosives/poussiéreuses|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FinType {
    /// Ailettes à persiennes (louvered) — technologie dominante pour chillers modernes.
    /// Corrélation Chang & Wang (1997) : j = 0.49 × (Lp/Fp)^0.27 × Re_Lp^(-0.49)
    Louvered,
    /// Ailettes ondulées (wavy/corrugated).
    Wavy,
    /// Ailettes à fentes (slit fins).
    Slit,
    /// Ailettes plates (plain fins) — application ambiances poussiéreuses.
    Plain,
}

impl FinType {
    /// Facteur multiplicatif sur le coefficient j de Colburn par rapport aux ailettes louvered.
    /// Valeurs calibrées sur données fabricants (Carrier, Trane, York).
    pub fn j_factor_relative(&self) -> f64 {
        match self {
            FinType::Louvered => 1.00,
            FinType::Slit => 0.93,
            FinType::Wavy => 0.78,
            FinType::Plain => 0.58,
        }
    }

    /// Nom textuel pour la sérialisation JSON.
    pub fn as_str(&self) -> &'static str {
        match self {
            FinType::Louvered => "louvered",
            FinType::Wavy => "wavy",
            FinType::Slit => "slit",
            FinType::Plain => "plain",
        }
    }

    /// Désérialisation depuis chaîne JSON.
    pub fn from_str(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "louvered" | "louver" | "lv" => FinType::Louvered,
            "wavy" | "wave" | "corrugated" => FinType::Wavy,
            "slit" => FinType::Slit,
            "plain" | "flat" => FinType::Plain,
            _ => FinType::Louvered, // Default sécurisé
        }
    }
}

/// Géométrie d'un condenseur RTPF.
///
/// Tous les paramètres par défaut correspondent à un condenseur de chiller
/// air-cooled industriel typique (Carrier 30XA / Trane RTAC).
#[derive(Debug, Clone)]
pub struct CoilGeometry {
    /// Largeur de face de bobine [m] (dimension horizontale du coil)
    pub face_width_m: f64,
    /// Hauteur de face de bobine [m] (dimension verticale)
    pub face_height_m: f64,
    /// Nombre de rangs de tubes (profondeur du coil)
    pub n_rows: u32,
    /// Type d'ailettes
    pub fin_type: FinType,
    /// Pas des ailettes [ailettes/pouce], typique : 10–18 FPI
    pub fin_pitch_fpi: f64,
    /// Épaisseur des ailettes aluminium [m] (défaut 0.1 mm)
    pub fin_thickness_m: f64,
    /// Diamètre extérieur des tubes [m] (défaut 9.52 mm = 3/8")
    pub tube_od_m: f64,
    /// Pas transversal des tubes (Pt) [m] (défaut 25.4 mm = 1")
    pub tube_pitch_transverse_m: f64,
    /// Pas longitudinal des tubes (Pl) [m] (défaut 22.0 mm, arrangement staggered)
    pub tube_pitch_longitudinal_m: f64,
    /// Louver pitch [m] — pour ailettes louvered (défaut 1.4 mm)
    pub louver_pitch_m: f64,
    /// Longueur des persiennes [m] — pour ailettes louvered (défaut 8.0 mm)
    pub louver_length_m: f64,
}

impl Default for CoilGeometry {
    fn default() -> Self {
        Self {
            face_width_m: 1.0,
            face_height_m: 1.0,
            n_rows: 3,
            fin_type: FinType::Louvered,
            fin_pitch_fpi: 14.0,
            fin_thickness_m: 0.0001,          // 0.1 mm
            tube_od_m: 0.009525,              // 9.525 mm = 3/8"
            tube_pitch_transverse_m: 0.0254,  // 25.4 mm = 1"
            tube_pitch_longitudinal_m: 0.022, // 22.0 mm staggered
            louver_pitch_m: 0.0014,           // 1.4 mm
            louver_length_m: 0.008,           // 8.0 mm
        }
    }
}

impl CoilGeometry {
    /// Construit une géométrie depuis la surface de face et le nombre de rangs.
    ///
    /// # Arguments
    ///
    /// * `face_width_m`  — Largeur de face [m]
    /// * `face_height_m` — Hauteur de face [m]  
    /// * `n_rows`        — Nombre de rangs de tubes
    pub fn new(face_width_m: f64, face_height_m: f64, n_rows: u32) -> Self {
        Self {
            face_width_m,
            face_height_m,
            n_rows,
            ..Default::default()
        }
    }

    /// Surface de face du coil [m²].
    pub fn face_area_m2(&self) -> f64 {
        self.face_width_m * self.face_height_m
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Composant principal
// ─────────────────────────────────────────────────────────────────────────────

/// Condenseur à air RTPF (Round Tube Plate Fin) — modèle physique complet.
///
/// L'ingénieur définit :
/// 1. La géométrie du coil (rangs, type d'ailettes, dimensions)
/// 2. Les conditions d'air (OAT, vitesse de face)
/// 3. La capacité nominale (pour le calcul de T_cond via ε-NTU)
///
/// Le composant calcule automatiquement :
/// - UA total à partir de la géométrie (corrélation Chang & Wang)
/// - Température de condensation T_cond via la méthode ε-NTU
///
/// # Exemple JSON
///
/// ```json
/// {
///   "type": "FinCoilCondenser",
///   "name": "cond_bat",
///   "oat_k": 308.15,
///   "face_width_m": 2.0,
///   "face_height_m": 1.5,
///   "n_rows": 3,
///   "fin_type": "louvered",
///   "fin_pitch_fpi": 14,
///   "air_face_velocity_m_s": 2.5,
///   "design_capacity_kw": 125.0
/// }
/// ```
///
/// # Exemple Rust
///
/// ```rust
/// use entropyk_components::heat_exchanger::{FinCoilCondenser, CoilGeometry, FinType};
///
/// let geometry = CoilGeometry {
///     face_width_m: 2.0,
///     face_height_m: 1.5,
///     n_rows: 3,
///     fin_type: FinType::Louvered,
///     fin_pitch_fpi: 14.0,
///     ..Default::default()
/// };
///
/// let cond = FinCoilCondenser::new(
///     308.15,   // OAT = 35°C [K]
///     geometry,
///     2.5,      // vitesse air [m/s]
///     125_000.0, // Q_design [W]
/// );
///
/// println!("UA = {:.0} W/K", cond.ua_total());
/// println!("T_cond = {:.1} °C", cond.t_cond_k() - 273.15);
/// println!("Approach = {:.1} K", cond.approach_k());
/// ```
pub struct FinCoilCondenser {
    inner: Condenser,

    // ── Inputs ingénieur ──────────────────────────────────────────────────
    oat_k: f64,
    geometry: CoilGeometry,
    air_face_velocity_m_s: f64,
    air_density_kg_m3: f64,
    design_capacity_w: f64,
    /// When true, apply Wang–Lin–Lee (2000) wet-surface j-factor correction.
    wet_surface: bool,

    // ── Résultats calculés ────────────────────────────────────────────────
    ua_air_side: f64,   // UA côté air [W/K]
    ua_total: f64,      // UA total (air + réfrigérant) [W/K]
    t_cond_k: f64,      // Température de condensation calculée [K]
    approach_k: f64,    // Approche = T_cond − OAT [K]
    ntu: f64,           // Nombre d'Unités de Transfert []
    effectiveness: f64, // Efficacité ε = 1 − exp(−NTU)
}

impl std::fmt::Debug for FinCoilCondenser {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FinCoilCondenser")
            .field("oat_k", &self.oat_k)
            .field("t_cond_k", &self.t_cond_k)
            .field("approach_k", &self.approach_k)
            .field("ua_total", &self.ua_total)
            .field("ntu", &self.ntu)
            .field("n_rows", &self.geometry.n_rows)
            .field("fin_type", &self.geometry.fin_type)
            .finish()
    }
}

impl FinCoilCondenser {
    // ─────────────────────────────────────────────────────────────────────
    // Constructeurs
    // ─────────────────────────────────────────────────────────────────────

    /// Construit un condenseur RTPF complet.
    ///
    /// # Arguments
    ///
    /// * `oat_k`              — Outdoor Air Temperature \[K\]
    /// * `geometry`           — Géométrie du coil (rangs, ailettes, tubes)
    /// * `air_face_velocity`  — Vitesse d'air en face de coil \[m/s\] (typique : 2.0–3.0)
    /// * `design_capacity_w`  — Capacité de rejet thermique au point nominal \[W\]
    pub fn new(
        oat_k: f64,
        geometry: CoilGeometry,
        air_face_velocity_m_s: f64,
        design_capacity_w: f64,
    ) -> Self {
        // Densité air à OAT (loi des gaz parfaits, P = 101325 Pa, R = 287 J/kg·K)
        let rho_air = 101_325.0 / (287.0 * oat_k);

        let mut s = Self {
            inner: Condenser::new(0.0), // UA sera mis à jour
            oat_k,
            geometry,
            air_face_velocity_m_s,
            air_density_kg_m3: rho_air,
            design_capacity_w,
            wet_surface: false,
            ua_air_side: 0.0,
            ua_total: 0.0,
            t_cond_k: oat_k + 15.0, // valeur initiale
            approach_k: 15.0,
            ntu: 0.0,
            effectiveness: 0.0,
        };
        s.recompute();
        s
    }

    /// Enables Wang–Lin–Lee wet-surface air-side correlation (cooling coils).
    pub fn with_wet_surface(mut self, wet: bool) -> Self {
        self.wet_surface = wet;
        self.recompute();
        self
    }

    /// Returns whether wet-surface air-side correlations are active.
    pub fn wet_surface(&self) -> bool {
        self.wet_surface
    }

    /// Attache un identifiant de fluide réfrigérant.
    pub fn with_refrigerant(mut self, id: &str) -> Self {
        self.inner = self.inner.with_refrigerant(id);
        self
    }

    /// Attache un backend de propriétés thermodynamiques.
    pub fn with_fluid_backend(mut self, backend: Arc<dyn FluidBackend>) -> Self {
        self.inner = self.inner.with_fluid_backend(backend);
        self
    }

    /// Surcharge la densité d'air (par ex. altitude élevée).
    pub fn with_air_density(mut self, rho: f64) -> Self {
        self.air_density_kg_m3 = rho;
        self.recompute();
        self
    }

    // ─────────────────────────────────────────────────────────────────────
    // Setters runtime (simulation paramétrique)
    // ─────────────────────────────────────────────────────────────────────

    /// Met à jour OAT et recalcule T_cond.
    pub fn set_oat_k(&mut self, oat_k: f64) {
        self.oat_k = oat_k;
        self.air_density_kg_m3 = 101_325.0 / (287.0 * oat_k);
        self.recompute();
    }

    /// Met à jour la vitesse d'air et recalcule UA + T_cond.
    pub fn set_air_face_velocity(&mut self, v: f64) {
        self.air_face_velocity_m_s = v;
        self.recompute();
    }

    /// Met à jour la capacité nominale et recalcule T_cond.
    pub fn set_design_capacity_w(&mut self, q_w: f64) {
        self.design_capacity_w = q_w;
        self.recompute();
    }

    // ─────────────────────────────────────────────────────────────────────
    // Getters
    // ─────────────────────────────────────────────────────────────────────

    /// Température de condensation calculée [K].
    pub fn t_cond_k(&self) -> f64 {
        self.t_cond_k
    }

    /// OAT [K].
    pub fn oat_k(&self) -> f64 {
        self.oat_k
    }

    /// Approche = T_cond − OAT [K].
    pub fn approach_k(&self) -> f64 {
        self.approach_k
    }

    /// UA côté air [W/K].
    pub fn ua_air_side(&self) -> f64 {
        self.ua_air_side
    }

    /// UA total (air + réfrigérant) [W/K].
    pub fn ua_total(&self) -> f64 {
        self.ua_total
    }

    /// Nombre d'unités de transfert NTU.
    pub fn ntu(&self) -> f64 {
        self.ntu
    }

    /// Efficacité ε du condenseur.
    pub fn effectiveness(&self) -> f64 {
        self.effectiveness
    }

    /// Débit massique d'air [kg/s].
    pub fn air_mass_flow_kg_s(&self) -> f64 {
        self.air_density_kg_m3 * self.geometry.face_area_m2() * self.air_face_velocity_m_s
    }

    /// Puissance côté air C_air = ṁ_air × Cp_air [W/K].
    pub fn c_air(&self) -> f64 {
        self.air_mass_flow_kg_s() * CP_AIR
    }

    /// Température de sortie de l'air [K].
    pub fn t_air_out_k(&self) -> f64 {
        self.oat_k + self.design_capacity_w / self.c_air().max(1.0)
    }

    /// Géométrie du coil.
    pub fn geometry(&self) -> &CoilGeometry {
        &self.geometry
    }

    /// UA [W/K] (alias vers ua_total pour compatibilité avec Condenser).
    pub fn ua(&self) -> f64 {
        self.ua_total
    }

    /// Facteurs de calibration.
    pub fn calib(&self) -> &Calib {
        self.inner.calib()
    }

    // ─────────────────────────────────────────────────────────────────────
    // Calcul interne : UA depuis géométrie + T_cond via ε-NTU
    // ─────────────────────────────────────────────────────────────────────

    /// Recalcule UA_air, UA_total et T_cond depuis les paramètres courants.
    ///
    /// Appelé automatiquement à la construction et lors de tout changement
    /// des paramètres (OAT, vitesse d'air, capacité nominale).
    fn recompute(&mut self) {
        self.ua_air_side = self.compute_ua_air();
        // Correction résistance réfrigérant (condensation ~ 8% de la résistance totale)
        self.ua_total = self.ua_air_side * REF_RESISTANCE_FACTOR;

        // ε-NTU pour condensation isotherme
        let c_air = self.c_air().max(1.0);
        self.ntu = self.ua_total / c_air;
        self.effectiveness = 1.0 - (-self.ntu).exp();

        // T_cond analytique (isothermal refrigerant, Chang & Wang 1997, App. B)
        // ΔT_air = Q / C_air
        // ε = ΔT_air / (T_cond - OAT)  →  T_cond = OAT + ΔT_air / ε
        let delta_t_air = self.design_capacity_w / c_air;
        self.t_cond_k = if self.effectiveness > 1e-6 {
            self.oat_k + delta_t_air / self.effectiveness
        } else {
            self.oat_k + 20.0 // fallback si ε ≈ 0
        };
        self.approach_k = self.t_cond_k - self.oat_k;

        // Mettre à jour le condenseur interne avec la nouvelle T_cond
        self.inner.set_saturation_temp(self.t_cond_k);
        self.inner.set_ua(self.ua_total);
    }

    /// Calcule le UA côté air via la corrélation Chang & Wang (1997) + efficacité ailettes.
    ///
    /// ## Algorithme
    ///
    /// 1. Géométrie → paramètres de maille (cellule unitaire)
    /// 2. Vitesse d'air interstitielle → Reynolds Re_Lp
    /// 3. Facteur j de Colburn → coefficient h_air
    /// 4. Efficacité ailettes η_fin (profil rectangulaire)
    /// 5. Efficacité de surface η_surface
    /// 6. UA_air = η_surface × h_air × A_total
    fn compute_ua_air(&self) -> f64 {
        let geom = &self.geometry;

        // ── Paramètres dérivés de la géométrie ───────────────────────────
        let fin_pitch_m = 0.0254 / geom.fin_pitch_fpi; // distance centre-à-centre entre ailettes [m]
        let n_rows = geom.n_rows as f64;

        // Nombre de tubes :
        //   - Dans la direction hauteur (transversale) : n_tubes_per_row = H / Pt
        //   - Profondeur (n_rows rangées) : n_tubes_total = n_per_row × n_rows
        //   - Chaque tube traverse TOUTE la largeur W (longueur de tube = face_width_m)
        let n_tubes_per_row = (geom.face_height_m / geom.tube_pitch_transverse_m)
            .floor()
            .max(1.0);
        let n_tubes = n_tubes_per_row * n_rows; // tubes au total dans le coil

        // ── Surfaces des ailettes ─────────────────────────────────────────
        // Chaque ailette : plaque H × coil_depth avec n_tubes trous
        //   coil_depth = n_rows × Pl   (Pl = pas longitudinal, profondeur du coil)
        //   A_fin_1_face = H × coil_depth − n_tubes × π×d²/4
        //   A_fin_2_faces = 2 × A_fin_1_face
        let coil_depth_m = n_rows * geom.tube_pitch_longitudinal_m;
        let tube_hole_area = n_tubes * std::f64::consts::PI * geom.tube_od_m.powi(2) / 4.0;
        let a_fin_per_fin = 2.0 * (geom.face_height_m * coil_depth_m - tube_hole_area).max(0.0);

        // Nombre d'ailettes sur la largeur du coil
        let n_fins = geom.face_width_m / fin_pitch_m;
        let a_fin_total = n_fins * a_fin_per_fin;

        // ── Surface nue des tubes (portions entre ailettes) ───────────────
        let tube_perimeter = std::f64::consts::PI * geom.tube_od_m;
        let tube_length_between_fins = fin_pitch_m - geom.fin_thickness_m;
        let a_tube_bare = n_tubes * tube_perimeter * tube_length_between_fins.max(0.0);

        let a_total = a_fin_total + a_tube_bare;

        // ── Vitesse d'air massique G [kg/(m²·s)] ─────────────────────────
        // Section libre minimale : σ = (Pt - d_tube) / Pt (approximation)
        let sigma = (geom.tube_pitch_transverse_m - geom.tube_od_m) / geom.tube_pitch_transverse_m;
        let g_air = self.air_density_kg_m3 * self.air_face_velocity_m_s / sigma.max(0.1);

        // ── Nombre de Reynolds basé sur louver pitch (Re_Lp) ─────────────
        let re_lp = g_air * geom.louver_pitch_m / MU_AIR;

        // ── Facteur j de Colburn (Chang & Wang 1997, Eq.4) ───────────────
        // j_louvered = 0.49 × (Lp/Fp)^0.27 × Re_Lp^(-0.49) × (Lh/Lp)^(-0.14) × (N)^(-0.29)
        // Version simplifiée (terme dominant) :
        let lp_over_fp = geom.louver_pitch_m / fin_pitch_m;
        let j_louvered = 0.49
            * lp_over_fp.powf(0.27)
            * re_lp.powf(-0.49)
            * (geom.louver_length_m / geom.louver_pitch_m).powf(-0.14)
            * n_rows.powf(-0.29);

        // Correction pour le type d'ailettes
        let mut j = j_louvered * geom.fin_type.j_factor_relative();

        // Wang, Lin & Lee (2000) wet-surface plain-fin correction (j_wet / j_dry ≈ 0.85–1.05).
        // Simplified multiplicative factor used when condensate films the fins.
        if self.wet_surface {
            let re_dc = g_air * geom.tube_od_m / MU_AIR;
            let j_wet_factor = 0.914 * re_dc.powf(-0.05);
            j *= j_wet_factor.clamp(0.7, 1.1);
        }

        // ── Coefficient convectif côté air h_air [W/(m²·K)] ─────────────
        let h_air = j * g_air * CP_AIR / PR_AIR.powf(2.0 / 3.0);

        // ── Efficacité des ailettes η_fin (profil rectangulaire) ─────────
        // Hauteur effective de l'ailette
        let l_fin = (geom.tube_pitch_transverse_m / 2.0 - geom.tube_od_m / 2.0)
            * (1.0 + 0.35 * (geom.tube_pitch_transverse_m / geom.tube_od_m).ln());
        let m = ((2.0 * h_air) / (K_FIN_ALU * geom.fin_thickness_m)).sqrt();
        let ml = (m * l_fin).max(1e-9);
        let eta_fin = if ml < 1e-6 { 1.0 } else { ml.tanh() / ml };

        // ── Efficacité de surface ─────────────────────────────────────────
        let eta_surface = if a_total > 0.0 {
            1.0 - (1.0 - eta_fin) * a_fin_total / a_total
        } else {
            1.0
        };

        // ── UA côté air ──────────────────────────────────────────────────
        let ua_air = eta_surface * h_air * a_total;

        // Garde-fous : valeur physiquement raisonnable
        ua_air.max(100.0).min(1_000_000.0)
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Implémentation du trait Component — délégation vers inner Condenser
// ─────────────────────────────────────────────────────────────────────────────

impl Component for FinCoilCondenser {
    fn set_system_context(
        &mut self,
        state_offset: usize,
        external_edge_state_indices: &[(usize, usize, usize)],
    ) {
        self.inner
            .set_system_context(state_offset, external_edge_state_indices);
    }

    fn compute_residuals(
        &self,
        state: &StateSlice,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        self.inner.compute_residuals(state, residuals)
    }

    fn jacobian_entries(
        &self,
        state: &StateSlice,
        jacobian: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        self.inner.jacobian_entries(state, jacobian)
    }

    fn n_equations(&self) -> usize {
        self.inner.n_equations()
    }

    fn get_ports(&self) -> &[ConnectedPort] {
        self.inner.get_ports()
    }

    fn set_fluid_backend_from_builder(&mut self, backend: Arc<dyn FluidBackend>) {
        self.inner.set_fluid_backend_from_builder(backend);
    }

    fn set_calib_indices(&mut self, indices: entropyk_core::CalibIndices) {
        self.inner.set_calib_indices(indices);
    }

    fn port_mass_flows(
        &self,
        state: &StateSlice,
    ) -> Result<Vec<entropyk_core::MassFlow>, ComponentError> {
        self.inner.port_mass_flows(state)
    }

    fn port_enthalpies(
        &self,
        state: &StateSlice,
    ) -> Result<Vec<entropyk_core::Enthalpy>, ComponentError> {
        self.inner.port_enthalpies(state)
    }

    fn energy_transfers(
        &self,
        state: &StateSlice,
    ) -> Option<(entropyk_core::Power, entropyk_core::Power)> {
        self.inner.energy_transfers(state)
    }

    fn signature(&self) -> String {
        format!(
            "FinCoilCondenser(oat={:.1}K, T_cond={:.1}K, approach={:.1}K, UA={:.0}W/K, NTU={:.2}, {}×{}rows)",
            self.oat_k,
            self.t_cond_k,
            self.approach_k,
            self.ua_total,
            self.ntu,
            self.geometry.fin_type.as_str(),
            self.geometry.n_rows,
        )
    }

    fn to_params(&self) -> crate::ComponentParams {
        self.inner.to_params()
    }

    fn update_calib_factor(&mut self, factor: &str, value: f64) -> bool {
        self.inner.update_calib_factor(factor, value)
    }
}

impl StateManageable for FinCoilCondenser {
    fn state(&self) -> OperationalState {
        self.inner.state()
    }

    fn set_state(&mut self, state: OperationalState) -> Result<(), ComponentError> {
        self.inner.set_state(state)
    }

    fn can_transition_to(&self, target: OperationalState) -> bool {
        self.inner.can_transition_to(target)
    }

    fn circuit_id(&self) -> &CircuitId {
        self.inner.circuit_id()
    }

    fn set_circuit_id(&mut self, circuit_id: CircuitId) {
        self.inner.set_circuit_id(circuit_id);
    }
}

// Rendre `design_capacity_w` accessible pour les tests
impl FinCoilCondenser {
    #[doc(hidden)]
    pub fn design_capacity_w(&self) -> f64 {
        self.design_capacity_w
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Tests
// ─────────────────────────────────────────────────────────────────────────────

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

    fn make_typical_condenser() -> FinCoilCondenser {
        // Chiller ~100 kW, condenseur 3 rangs, 14 FPI louvered, OAT=35°C
        let geom = CoilGeometry {
            face_width_m: 2.0,
            face_height_m: 1.5,
            n_rows: 3,
            fin_type: FinType::Louvered,
            fin_pitch_fpi: 14.0,
            ..Default::default()
        };
        FinCoilCondenser::new(308.15, geom, 2.5, 125_000.0)
    }

    #[test]
    fn test_n_equations() {
        // CM1.3: 2 thermo + 1 mass-flow = 3 (delegates to inner Condenser)
        assert_eq!(make_typical_condenser().n_equations(), 3);
    }

    #[test]
    fn test_ua_positive() {
        let c = make_typical_condenser();
        assert!(
            c.ua_total() > 1000.0,
            "UA should be > 1 kW/K, got {}",
            c.ua_total()
        );
        assert!(
            c.ua_total() < 200_000.0,
            "UA unrealistically high: {}",
            c.ua_total()
        );
    }

    #[test]
    fn test_approach_realistic() {
        let c = make_typical_condenser();
        // Chiller air-cooled : approach typique 10–25 K
        assert!(
            c.approach_k() > 5.0 && c.approach_k() < 35.0,
            "Approach unrealistic: {:.1} K (UA={:.0} W/K)",
            c.approach_k(),
            c.ua_total()
        );
    }

    #[test]
    fn test_t_cond_above_oat() {
        let c = make_typical_condenser();
        assert!(
            c.t_cond_k() > c.oat_k(),
            "T_cond ({:.1} K) must be > OAT ({:.1} K)",
            c.t_cond_k(),
            c.oat_k()
        );
    }

    #[test]
    fn test_more_rows_lower_approach() {
        let geom3 = CoilGeometry {
            n_rows: 3,
            face_width_m: 2.0,
            face_height_m: 1.5,
            ..Default::default()
        };
        let geom4 = CoilGeometry {
            n_rows: 4,
            face_width_m: 2.0,
            face_height_m: 1.5,
            ..Default::default()
        };
        let c3 = FinCoilCondenser::new(308.15, geom3, 2.5, 125_000.0);
        let c4 = FinCoilCondenser::new(308.15, geom4, 2.5, 125_000.0);
        assert!(
            c4.approach_k() < c3.approach_k(),
            "More rows should reduce approach: 3rows={:.1}K, 4rows={:.1}K",
            c3.approach_k(),
            c4.approach_k()
        );
    }

    #[test]
    fn test_higher_velocity_lower_approach() {
        let geom = CoilGeometry {
            n_rows: 3,
            face_width_m: 2.0,
            face_height_m: 1.5,
            ..Default::default()
        };
        let c_slow = FinCoilCondenser::new(308.15, geom.clone(), 2.0, 125_000.0);
        let c_fast = FinCoilCondenser::new(308.15, geom, 3.5, 125_000.0);
        assert!(
            c_fast.approach_k() < c_slow.approach_k(),
            "Higher velocity should reduce approach: 2m/s={:.1}K, 3.5m/s={:.1}K",
            c_slow.approach_k(),
            c_fast.approach_k()
        );
    }

    #[test]
    fn test_louvered_vs_plain_fins() {
        let geom_louv = CoilGeometry {
            n_rows: 3,
            face_width_m: 2.0,
            face_height_m: 1.5,
            fin_type: FinType::Louvered,
            ..Default::default()
        };
        let geom_plain = CoilGeometry {
            n_rows: 3,
            face_width_m: 2.0,
            face_height_m: 1.5,
            fin_type: FinType::Plain,
            ..Default::default()
        };
        let c_louv = FinCoilCondenser::new(308.15, geom_louv, 2.5, 125_000.0);
        let c_plain = FinCoilCondenser::new(308.15, geom_plain, 2.5, 125_000.0);
        assert!(
            c_louv.ua_total() > c_plain.ua_total(),
            "Louvered fins should give higher UA than plain"
        );
    }

    #[test]
    fn test_set_oat_updates_t_cond() {
        let mut c = make_typical_condenser();
        let t_cond_35 = c.t_cond_k();
        c.set_oat_k(313.15); // OAT = 40°C
        let t_cond_40 = c.t_cond_k();
        assert!(
            t_cond_40 > t_cond_35,
            "T_cond should increase with OAT: 35°C→{:.1}K, 40°C→{:.1}K",
            t_cond_35,
            t_cond_40
        );
    }

    #[test]
    fn test_epsilon_ntu_consistency() {
        let c = make_typical_condenser();
        // Vérification : ε × (T_cond - OAT) = ΔT_air = Q/C_air
        let delta_t_air_check = c.effectiveness() * c.approach_k();
        let delta_t_air_ref = c.design_capacity_w / c.c_air();
        assert!(
            (delta_t_air_check - delta_t_air_ref).abs() < 0.01,
            "ε-NTU consistency error: {:.3} vs {:.3}",
            delta_t_air_check,
            delta_t_air_ref
        );
    }

    #[test]
    fn test_fin_type_from_str() {
        assert_eq!(FinType::from_str("louvered"), FinType::Louvered);
        assert_eq!(FinType::from_str("wavy"), FinType::Wavy);
        assert_eq!(FinType::from_str("plain"), FinType::Plain);
        assert_eq!(FinType::from_str("SLIT"), FinType::Slit);
        assert_eq!(FinType::from_str("unknown"), FinType::Louvered); // default
    }
}
