//! Evaporator Coil Component
//!
//! An air-side (finned) heat exchanger for refrigerant evaporation.
//! The refrigerant (cold side) evaporates, absorbing heat from air (hot side).
//! Used in split systems and air-source heat pumps.
//!
//! ## Port Convention
//!
//! - **Hot side (air)**: Heat source — connect to Fan outlet/inlet
//! - **Cold side (refrigerant)**: Evaporating
//!
//! ## Integration with Fan
//!
//! Connect Fan outlet → EvaporatorCoil air inlet, EvaporatorCoil air outlet → Fan inlet.
//! Use `FluidId::new("Air")` for air ports.

use super::evaporator::Evaporator;
use crate::state_machine::{CircuitId, OperationalState, StateManageable};
use crate::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
};

/// Evaporator coil (air-side finned heat exchanger).
///
/// Explicit component for air-source evaporators. Uses ε-NTU method.
/// Refrigerant evaporates on cold side, air on hot side.
///
/// # Example
///
/// ```
/// use entropyk_components::heat_exchanger::EvaporatorCoil;
/// use entropyk_components::Component;
///
/// let coil = EvaporatorCoil::new(8_000.0);  // UA = 8 kW/K
/// assert_eq!(coil.ua(), 8_000.0);
/// assert_eq!(coil.n_equations(), 3); // 2 thermo + 1 mass-flow (CM1.3)
/// ```
#[derive(Debug)]
pub struct EvaporatorCoil {
    inner: Evaporator,
    air_validated: std::sync::atomic::AtomicBool,
}

impl EvaporatorCoil {
    /// Creates a new evaporator coil with the given UA value.
    ///
    /// # Arguments
    ///
    /// * `ua` - Overall heat transfer coefficient × Area (W/K)
    pub fn new(ua: f64) -> Self {
        Self {
            inner: Evaporator::new(ua),
            air_validated: std::sync::atomic::AtomicBool::new(false),
        }
    }

    /// Creates an evaporator coil with specific saturation and superheat.
    pub fn with_superheat(ua: f64, saturation_temp: f64, superheat_target: f64) -> Self {
        Self {
            inner: Evaporator::with_superheat(ua, saturation_temp, superheat_target),
            air_validated: std::sync::atomic::AtomicBool::new(false),
        }
    }

    /// Returns the name of this component.
    pub fn name(&self) -> &str {
        "EvaporatorCoil"
    }

    /// Returns the UA value.
    pub fn ua(&self) -> f64 {
        self.inner.ua()
    }

    /// Returns the saturation temperature.
    pub fn saturation_temp(&self) -> f64 {
        self.inner.saturation_temp()
    }

    /// Returns the superheat target.
    pub fn superheat_target(&self) -> f64 {
        self.inner.superheat_target()
    }

    /// Sets the saturation temperature.
    pub fn set_saturation_temp(&mut self, temp: f64) {
        self.inner.set_saturation_temp(temp);
    }

    /// Sets the superheat target.
    pub fn set_superheat_target(&mut self, superheat: f64) {
        self.inner.set_superheat_target(superheat);
    }
}

impl Component for EvaporatorCoil {
    fn compute_residuals(
        &self,
        state: &StateSlice,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        if !self
            .air_validated
            .load(std::sync::atomic::Ordering::Relaxed)
        {
            if let Some(fluid_id) = self.inner.hot_fluid_id() {
                if fluid_id.0.as_str() != "Air" {
                    return Err(ComponentError::InvalidState(format!(
                        "EvaporatorCoil requires Air on the hot side, found {}",
                        fluid_id.0.as_str()
                    )));
                }
                self.air_validated
                    .store(true, std::sync::atomic::Ordering::Relaxed);
            }
        }
        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_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 {
        self.inner.signature()
    }

    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 EvaporatorCoil {
    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);
    }
}

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

    #[test]
    fn test_evaporator_coil_creation() {
        let coil = EvaporatorCoil::new(8_000.0);
        assert_eq!(coil.ua(), 8_000.0);
        assert_eq!(coil.name(), "EvaporatorCoil");
    }

    #[test]
    fn test_evaporator_coil_n_equations() {
        let coil = EvaporatorCoil::new(5_000.0);
        // CM1.3: 2 thermo + 1 mass-flow = 3 (delegates to inner Evaporator)
        assert_eq!(coil.n_equations(), 3);
    }

    #[test]
    fn test_evaporator_coil_with_superheat() {
        let coil = EvaporatorCoil::with_superheat(8_000.0, 278.15, 5.0);
        assert_eq!(coil.saturation_temp(), 278.15);
        assert_eq!(coil.superheat_target(), 5.0);
    }

    #[test]
    fn test_evaporator_coil_compute_residuals() {
        let coil = EvaporatorCoil::new(8_000.0);
        let state = vec![0.0; 10];
        let mut residuals = vec![0.0; 3];
        let result = coil.compute_residuals(&state, &mut residuals);
        assert!(matches!(result, Err(ComponentError::InvalidState(_))));
    }

    #[test]
    fn test_evaporator_coil_rejects_non_air() {
        use crate::heat_exchanger::HxSideConditions;
        use entropyk_core::{MassFlow, Pressure, Temperature};

        let mut coil = EvaporatorCoil::new(8_000.0);

        coil.inner.set_hot_conditions(
            HxSideConditions::new(
                Temperature::from_celsius(20.0),
                Pressure::from_bar(1.0),
                MassFlow::from_kg_per_s(1.0),
                "Water",
            )
            .expect("Valid hot conditions"),
        );

        let state = vec![0.0; 10];
        let mut residuals = vec![0.0; 3];
        let result = coil.compute_residuals(&state, &mut residuals);

        assert!(result.is_err());
        if let Err(ComponentError::InvalidState(msg)) = result {
            assert!(msg.contains("requires Air"));
        } else {
            panic!("Expected InvalidState error");
        }
    }

    #[test]
    fn test_evaporator_coil_jacobian_entries() {
        let coil = EvaporatorCoil::new(8_000.0);
        let state = vec![0.0; 10];
        let mut jacobian = crate::JacobianBuilder::new();
        let result = coil.jacobian_entries(&state, &mut jacobian);
        assert!(result.is_ok());
        // HeatExchanger base returns empty jacobian until framework implements it
        assert!(
            jacobian.is_empty(),
            "delegation works; empty jacobian expected until HeatExchanger implements entries"
        );
    }

    #[test]
    fn test_evaporator_coil_setters() {
        let mut coil = EvaporatorCoil::new(8_000.0);
        coil.set_saturation_temp(275.0);
        coil.set_superheat_target(7.0);
        assert!((coil.saturation_temp() - 275.0).abs() < 1e-10);
        assert!((coil.superheat_target() - 7.0).abs() < 1e-10);
    }

    #[test]
    fn test_evaporator_coil_state_manageable() {
        use crate::state_machine::{OperationalState, StateManageable};

        let mut coil = EvaporatorCoil::new(8_000.0);
        assert_eq!(coil.state(), OperationalState::On);
        assert!(coil.can_transition_to(OperationalState::Off));
        assert!(coil.set_state(OperationalState::Off).is_ok());
        assert_eq!(coil.state(), OperationalState::Off);
    }
}
