//! Heat Transfer Model Trait
//!
//! Defines the Strategy Pattern interface for heat transfer calculations.
//! This trait is object-safe for dynamic dispatch.

use crate::ResidualVector;
use entropyk_core::{Enthalpy, MassFlow, Power, Pressure, Temperature};

/// Fluid state for heat transfer calculations.
///
/// Represents the thermodynamic state at a port (inlet or outlet).
#[derive(Debug, Clone, Copy)]
pub struct FluidState {
    /// Temperature in Kelvin
    pub temperature: f64,
    /// Pressure in Pascals
    pub pressure: f64,
    /// Specific enthalpy in J/kg
    pub enthalpy: f64,
    /// Mass flow rate in kg/s
    pub mass_flow: f64,
    /// Specific heat capacity at constant pressure in J/(kg·K)
    pub cp: f64,
}

impl Default for FluidState {
    fn default() -> Self {
        Self {
            temperature: 300.0,
            pressure: 101_325.0,
            enthalpy: 0.0,
            mass_flow: 0.1,
            cp: 1000.0,
        }
    }
}

impl FluidState {
    /// Creates a new fluid state.
    pub fn new(temperature: f64, pressure: f64, enthalpy: f64, mass_flow: f64, cp: f64) -> Self {
        Self {
            temperature,
            pressure,
            enthalpy,
            mass_flow,
            cp,
        }
    }

    /// Creates a fluid state with default properties for a given temperature.
    pub fn from_temperature(temperature: f64) -> Self {
        Self {
            temperature,
            ..Default::default()
        }
    }

    /// Returns the heat capacity rate C = ṁ × Cp in W/K.
    pub fn heat_capacity_rate(&self) -> f64 {
        self.mass_flow * self.cp
    }

    /// Creates a FluidState from strongly-typed physical quantities.
    pub fn from_types(
        temperature: Temperature,
        pressure: Pressure,
        enthalpy: Enthalpy,
        mass_flow: MassFlow,
        cp: f64,
    ) -> Self {
        Self {
            temperature: temperature.to_kelvin(),
            pressure: pressure.to_pascals(),
            enthalpy: enthalpy.to_joules_per_kg(),
            mass_flow: mass_flow.to_kg_per_s(),
            cp,
        }
    }

    /// Returns temperature as a strongly-typed Temperature.
    pub fn temperature(&self) -> Temperature {
        Temperature::from_kelvin(self.temperature)
    }

    /// Returns pressure as a strongly-typed Pressure.
    pub fn pressure(&self) -> Pressure {
        Pressure::from_pascals(self.pressure)
    }

    /// Returns enthalpy as a strongly-typed Enthalpy.
    pub fn enthalpy(&self) -> Enthalpy {
        Enthalpy::from_joules_per_kg(self.enthalpy)
    }

    /// Returns mass flow as a strongly-typed MassFlow.
    pub fn mass_flow(&self) -> MassFlow {
        MassFlow::from_kg_per_s(self.mass_flow)
    }
}

/// Trait for heat transfer calculation models.
///
/// This trait uses the Strategy Pattern to allow different heat transfer
/// calculation methods (LMTD, ε-NTU, etc.) to be used interchangeably.
///
/// # Object Safety
///
/// This trait is object-safe and can be used with dynamic dispatch:
///
/// ```
/// # use entropyk_components::heat_exchanger::model::{HeatTransferModel, FluidState};
/// # use entropyk_components::ResidualVector;
/// # use entropyk_core::Power;
/// struct SimpleModel { ua: f64 }
/// impl HeatTransferModel for SimpleModel {
///     fn compute_heat_transfer(&self, _: &FluidState, _: &FluidState, _: &FluidState, _: &FluidState, _: Option<f64>) -> Power {
///         Power::from_watts(0.0)
///     }
///     fn compute_residuals(&self, _: &FluidState, _: &FluidState, _: &FluidState, _: &FluidState, _: &mut ResidualVector, _: Option<f64>) {}
///     fn n_equations(&self) -> usize { 3 }
///     fn ua(&self) -> f64 { self.ua }
/// }
/// let model: Box<dyn HeatTransferModel> = Box::new(SimpleModel { ua: 1000.0 });
/// ```
pub trait HeatTransferModel: Send + Sync {
    /// Computes the heat transfer rate Q̇ (Watts).
    ///
    /// # Arguments
    ///
    /// * `hot_inlet` - Hot side inlet state
    /// * `hot_outlet` - Hot side outlet state
    /// * `cold_inlet` - Cold side inlet state
    /// * `cold_outlet` - Cold side outlet state
    ///
    /// # Returns
    ///
    /// The heat transfer rate in Watts (positive = heat flows from hot to cold)
    fn compute_heat_transfer(
        &self,
        hot_inlet: &FluidState,
        hot_outlet: &FluidState,
        cold_inlet: &FluidState,
        cold_outlet: &FluidState,
        dynamic_ua_scale: Option<f64>,
    ) -> Power;

    /// Computes residuals for the solver.
    ///
    /// The residuals represent the error in the heat transfer equations
    /// that the solver will attempt to drive to zero.
    fn compute_residuals(
        &self,
        hot_inlet: &FluidState,
        hot_outlet: &FluidState,
        cold_inlet: &FluidState,
        cold_outlet: &FluidState,
        residuals: &mut ResidualVector,
        dynamic_ua_scale: Option<f64>,
    );

    /// Returns the number of equations this model contributes.
    fn n_equations(&self) -> usize;

    /// Returns the nominal UA value (overall heat transfer coefficient × area) in W/K.
    fn ua(&self) -> f64;

    /// Returns the UA calibration scale (default 1.0). UA_eff = ua_scale × ua_nominal.
    fn ua_scale(&self) -> f64 {
        1.0
    }

    /// Sets the UA calibration scale (e.g. from Calib.z_ua).
    fn set_ua_scale(&mut self, _s: f64) {}

    /// Returns the effective UA used in heat transfer. If dynamic_ua_scale is provided, it is used instead of ua_scale.
    fn effective_ua(&self, dynamic_ua_scale: Option<f64>) -> f64 {
        self.ua() * dynamic_ua_scale.unwrap_or_else(|| self.ua_scale())
    }
}

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

    #[test]
    fn test_fluid_state_default() {
        let state = FluidState::default();
        assert_eq!(state.temperature, 300.0);
        assert_eq!(state.pressure, 101_325.0);
        assert_eq!(state.cp, 1000.0);
    }

    #[test]
    fn test_fluid_state_heat_capacity_rate() {
        let state = FluidState::new(300.0, 101_325.0, 0.0, 0.5, 2000.0);
        let c = state.heat_capacity_rate();
        assert!((c - 1000.0).abs() < 1e-10);
    }

    #[test]
    fn test_fluid_state_from_temperature() {
        let state = FluidState::from_temperature(350.0);
        assert_eq!(state.temperature, 350.0);
        assert_eq!(state.pressure, 101_325.0);
    }
}
