//! FloodedEvaporator - Flooded (recirculation) evaporator component
//!
//! Models a heat exchanger where liquid refrigerant floods the tubes,
//! typically used in industrial chillers with recirculation systems.
//! Output quality is typically 0.5-0.8 (two-phase), not superheated.
//!
//! ## Difference from DX Evaporator
//!
//! - DX Evaporator: Output is superheated vapor (x >= 1), controlled by superheat
//! - FloodedEvaporator: Output is two-phase (x ~ 0.5-0.8), controlled by quality
//!
//! ## Example
//!
//! ```ignore
//! use entropyk_components::heat_exchanger::FloodedEvaporator;
//! use entropyk_core::MassFlow;
//!
//! let evap = FloodedEvaporator::new(10_000.0)
//!     .with_target_quality(0.7);
//!
//! assert_eq!(evap.n_equations(), 3);
//! ```

use super::eps_ntu::{EpsNtuModel, ExchangerType};
use super::exchanger::{HeatExchanger, HxSideConditions};
use crate::state_machine::{CircuitId, OperationalState, StateManageable};
use crate::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_core::{Calib, MassFlow, Power, Pressure};
use entropyk_fluids::{FluidBackend, FluidId, FluidState, Property, Quality};
use std::sync::Arc;

/// Minimum valid UA value (W/K).
const MIN_UA: f64 = 0.0;

/// FloodedEvaporator - Heat exchanger for flooded (recirculation) systems.
///
/// Uses the epsilon-NTU method for heat transfer calculation.
/// Output quality is typically 0.5-0.8 (two-phase mixture).
pub struct FloodedEvaporator {
    inner: HeatExchanger<EpsNtuModel>,
    refrigerant_id: String,
    secondary_fluid_id: String,
    fluid_backend: Option<Arc<dyn FluidBackend>>,
    target_quality: f64,
    quality_control_enabled: bool,
    last_heat_transfer_w: f64,
    last_outlet_quality: Option<f64>,
    outlet_pressure_idx: Option<usize>,
    outlet_enthalpy_idx: Option<usize>,
}

impl std::fmt::Debug for FloodedEvaporator {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FloodedEvaporator")
            .field("ua", &self.ua())
            .field("refrigerant_id", &self.refrigerant_id)
            .field("secondary_fluid_id", &self.secondary_fluid_id)
            .field("target_quality", &self.target_quality)
            .field("quality_control_enabled", &self.quality_control_enabled)
            .field("has_fluid_backend", &self.fluid_backend.is_some())
            .finish()
    }
}

impl FloodedEvaporator {
    /// Creates a new flooded evaporator with the given UA value.
    ///
    /// # Arguments
    ///
    /// * `ua` - Overall heat transfer coefficient x Area (W/K). Must be >= 0.
    ///
    /// # Panics
    ///
    /// Panics if `ua` is negative.
    pub fn new(ua: f64) -> Self {
        assert!(ua >= MIN_UA, "UA must be non-negative, got {}", ua);
        let model = EpsNtuModel::new(ua, ExchangerType::CounterFlow);
        Self {
            inner: HeatExchanger::new(model, "FloodedEvaporator"),
            refrigerant_id: String::new(),
            secondary_fluid_id: String::new(),
            fluid_backend: None,
            target_quality: 0.7,
            quality_control_enabled: false,
            last_heat_transfer_w: 0.0,
            last_outlet_quality: None,
            outlet_pressure_idx: None,
            outlet_enthalpy_idx: None,
        }
    }

    /// Sets the refrigerant fluid identifier.
    pub fn with_refrigerant(mut self, fluid: impl Into<String>) -> Self {
        self.refrigerant_id = fluid.into();
        self
    }

    /// Sets the secondary fluid identifier.
    pub fn with_secondary_fluid(mut self, fluid: impl Into<String>) -> Self {
        self.secondary_fluid_id = fluid.into();
        self
    }

    /// Attaches a fluid backend for saturation calculations.
    pub fn with_fluid_backend(mut self, backend: Arc<dyn FluidBackend>) -> Self {
        self.fluid_backend = Some(backend);
        self
    }

    /// Sets the target outlet quality (0.0 to 1.0).
    ///
    /// Default is 0.7 (70% vapor, typical for flooded evaporators).
    pub fn with_target_quality(mut self, quality: f64) -> Self {
        self.target_quality = quality.clamp(0.0, 1.0);
        self
    }

    /// Enables quality control equation (adds 1 equation).
    ///
    /// When enabled, the solver will adjust variables to achieve target quality.
    pub fn with_quality_control(mut self, enabled: bool) -> Self {
        self.quality_control_enabled = enabled;
        self
    }

    /// Returns the component name.
    pub fn name(&self) -> &str {
        self.inner.name()
    }

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

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

    /// Sets calibration factors.
    pub fn set_calib(&mut self, calib: Calib) {
        self.inner.set_calib(calib);
    }

    /// Returns the target outlet quality.
    pub fn target_quality(&self) -> f64 {
        self.target_quality
    }

    /// Sets the target outlet quality.
    pub fn set_target_quality(&mut self, quality: f64) {
        self.target_quality = quality.clamp(0.0, 1.0);
    }

    /// Returns the last computed heat transfer rate (W).
    ///
    /// Returns 0.0 if `compute_residuals` has not been called.
    pub fn heat_transfer(&self) -> f64 {
        self.last_heat_transfer_w
    }

    /// Returns the last computed outlet quality.
    ///
    /// Returns `None` if `compute_residuals` has not been called or
    /// quality could not be computed (no FluidBackend).
    pub fn outlet_quality(&self) -> Option<f64> {
        self.last_outlet_quality
    }

    /// Sets the outlet state indices for quality control.
    ///
    /// These indices point to the pressure and enthalpy in the global state vector
    /// that represent the refrigerant outlet conditions.
    pub fn set_outlet_indices(&mut self, p_idx: usize, h_idx: usize) {
        self.outlet_pressure_idx = Some(p_idx);
        self.outlet_enthalpy_idx = Some(h_idx);
    }

    /// Sets the hot side (secondary fluid) boundary conditions.
    pub fn set_secondary_conditions(&mut self, conditions: HxSideConditions) {
        self.inner.set_hot_conditions(conditions);
    }

    /// Sets the cold side (refrigerant) boundary conditions.
    pub fn set_refrigerant_conditions(&mut self, conditions: HxSideConditions) {
        self.inner.set_cold_conditions(conditions);
    }

    /// Computes outlet quality from enthalpy and saturation properties.
    ///
    /// Returns `None` if:
    /// - No FluidBackend is configured
    /// - Refrigerant ID is empty
    /// - Saturation properties cannot be computed
    fn compute_quality(&self, h_out: f64, p_pa: f64) -> Option<f64> {
        if self.refrigerant_id.is_empty() {
            return None;
        }
        let backend = self.fluid_backend.as_ref()?;
        let fluid = FluidId::new(&self.refrigerant_id);
        let p = Pressure::from_pascals(p_pa);

        let h_sat_l = backend
            .property(
                fluid.clone(),
                Property::Enthalpy,
                FluidState::from_px(p, Quality::new(0.0)),
            )
            .ok()?;
        let h_sat_v = backend
            .property(
                fluid,
                Property::Enthalpy,
                FluidState::from_px(p, Quality::new(1.0)),
            )
            .ok()?;

        if h_sat_v > h_sat_l {
            let quality = (h_out - h_sat_l) / (h_sat_v - h_sat_l);
            Some(quality.clamp(0.0, 1.0))
        } else {
            // Invalid saturation envelope - return None
            None
        }
    }

    /// Validates that outlet is in two-phase region (x < 1).
    ///
    /// Returns Err if outlet is superheated (should use DX Evaporator instead).
    ///
    /// # Errors
    ///
    /// - `InvalidState`: Quality >= 1.0 (superheated outlet)
    /// - `CalculationFailed`: No FluidBackend configured or empty refrigerant ID
    pub fn validate_outlet_quality(&self, h_out: f64, p_pa: f64) -> Result<f64, ComponentError> {
        if self.refrigerant_id.is_empty() {
            return Err(ComponentError::InvalidState(
                "FloodedEvaporator: refrigerant_id not set".to_string(),
            ));
        }
        if self.fluid_backend.is_none() {
            return Err(ComponentError::CalculationFailed(
                "FloodedEvaporator: FluidBackend not configured".to_string(),
            ));
        }
        match self.compute_quality(h_out, p_pa) {
            Some(q) if q < 1.0 => Ok(q),
            Some(q) => Err(ComponentError::InvalidState(format!(
                "FloodedEvaporator outlet quality {:.2} >= 1.0 (superheated). Use DX Evaporator instead.",
                q
            ))),
            None => Err(ComponentError::CalculationFailed(format!(
                "FloodedEvaporator: Cannot compute quality for {} at P={:.0} Pa",
                self.refrigerant_id, p_pa
            ))),
        }
    }
}

impl Component for FloodedEvaporator {
    fn n_equations(&self) -> usize {
        let base = 2;
        if self.quality_control_enabled {
            base + 1
        } else {
            base
        }
    }

    fn compute_residuals(
        &self,
        state: &StateSlice,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        let mut inner_residuals = vec![0.0; 3];
        self.inner.compute_residuals(state, &mut inner_residuals)?;

        residuals[0] = inner_residuals[0];
        residuals[1] = inner_residuals[1];

        if self.quality_control_enabled {
            if let (Some(p_idx), Some(h_idx)) = (self.outlet_pressure_idx, self.outlet_enthalpy_idx)
            {
                let p_pa = *state.get(p_idx).unwrap_or(&0.0);
                let h_out = *state.get(h_idx).unwrap_or(&0.0);

                if let Some(actual_quality) = self.compute_quality(h_out, p_pa) {
                    residuals[2] = actual_quality - self.target_quality;
                } else {
                    // If quality cannot be computed, set residual to a large value
                    // or 0.0 depending on desired solver behavior.
                    // For now, let's assume it means target quality is not met.
                    residuals[2] = -self.target_quality; // Or some other error indicator
                }
            } else {
                // If indices are not set, quality control cannot be applied.
                // This should ideally be caught earlier or result in an error.
                // For now, set residual to indicate target quality is not met.
                residuals[2] = -self.target_quality;
            }
        }

        Ok(())
    }

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

    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<MassFlow>, ComponentError> {
        self.inner.port_mass_flows(state)
    }

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

    fn signature(&self) -> String {
        format!(
            "FloodedEvaporator(UA={:.0},fluid={},target_q={:.2})",
            self.ua(),
            self.refrigerant_id,
            self.target_quality
        )
    }
}

impl StateManageable for FloodedEvaporator {
    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_flooded_evaporator_creation() {
        let mut evap = FloodedEvaporator::new(10_000.0);
        assert_eq!(evap.ua(), 10_000.0);
        assert_eq!(evap.n_equations(), 2);
        assert_eq!(evap.target_quality(), 0.7);
        assert_eq!(evap.outlet_quality(), None);

        // with quality control
        evap.quality_control_enabled = true;
        assert_eq!(evap.n_equations(), 3);
    }

    #[test]
    #[should_panic(expected = "UA must be non-negative")]
    fn test_flooded_evaporator_negative_ua_panics() {
        let _evap = FloodedEvaporator::new(-100.0);
    }

    #[test]
    fn test_flooded_evaporator_zero_ua_allowed() {
        let evap = FloodedEvaporator::new(0.0);
        assert_eq!(evap.ua(), 0.0);
    }

    #[test]
    fn test_flooded_evaporator_with_quality_control() {
        let evap = FloodedEvaporator::new(10_000.0).with_quality_control(true);
        assert_eq!(evap.n_equations(), 3);
    }

    #[test]
    fn test_flooded_evaporator_with_target_quality() {
        let evap = FloodedEvaporator::new(10_000.0).with_target_quality(0.6);
        assert_eq!(evap.target_quality(), 0.6);
    }

    #[test]
    fn test_flooded_evaporator_clamps_quality() {
        let evap = FloodedEvaporator::new(10_000.0).with_target_quality(1.5);
        assert_eq!(evap.target_quality(), 1.0);

        let evap = FloodedEvaporator::new(10_000.0).with_target_quality(-0.5);
        assert_eq!(evap.target_quality(), 0.0);
    }

    #[test]
    fn test_flooded_evaporator_compute_residuals() {
        let evap = FloodedEvaporator::new(10_000.0);
        let state = vec![0.0; 10];
        let mut residuals = vec![0.0; 2];

        let result = evap.compute_residuals(&state, &mut residuals);
        assert!(result.is_ok());
    }

    #[test]
    fn test_flooded_evaporator_state_manageable() {
        let evap = FloodedEvaporator::new(10_000.0);
        assert_eq!(evap.state(), OperationalState::On);
        assert!(evap.can_transition_to(OperationalState::Off));
    }

    #[test]
    fn test_flooded_evaporator_set_state() {
        let mut evap = FloodedEvaporator::new(10_000.0);
        assert_eq!(evap.state(), OperationalState::On);

        let result = evap.set_state(OperationalState::Off);
        assert!(result.is_ok());
        assert_eq!(evap.state(), OperationalState::Off);

        let result = evap.set_state(OperationalState::Bypass);
        assert!(result.is_ok());
        assert_eq!(evap.state(), OperationalState::Bypass);
    }

    #[test]
    fn test_flooded_evaporator_signature() {
        let evap = FloodedEvaporator::new(10_000.0)
            .with_refrigerant("R410A")
            .with_target_quality(0.75);

        let sig = evap.signature();
        assert!(sig.contains("FloodedEvaporator"));
        assert!(sig.contains("R410A"));
        assert!(sig.contains("0.75"));
    }

    #[test]
    fn test_flooded_evaporator_set_target_quality() {
        let mut evap = FloodedEvaporator::new(10_000.0);
        evap.set_target_quality(0.65);
        assert_eq!(evap.target_quality(), 0.65);
    }

    #[test]
    fn test_flooded_evaporator_energy_transfers() {
        let evap = FloodedEvaporator::new(10_000.0);
        let state = vec![0.0; 10];
        let (heat, work) = evap.energy_transfers(&state).unwrap();
        assert_eq!(heat.to_watts(), 0.0);
        assert_eq!(work.to_watts(), 0.0);
    }

    #[test]
    fn test_flooded_evaporator_with_refrigerant() {
        let evap = FloodedEvaporator::new(10_000.0).with_refrigerant("R134a");
        let sig = evap.signature();
        assert!(sig.contains("R134a"));
    }

    #[test]
    fn test_flooded_evaporator_with_secondary_fluid() {
        let evap = FloodedEvaporator::new(10_000.0).with_secondary_fluid("Water");
        let debug_str = format!("{:?}", evap);
        assert!(debug_str.contains("Water"));
    }

    #[test]
    fn test_validate_outlet_quality_no_refrigerant() {
        let evap = FloodedEvaporator::new(10_000.0);
        let result = evap.validate_outlet_quality(400_000.0, 300_000.0);
        assert!(result.is_err());
        match result {
            Err(ComponentError::InvalidState(msg)) => {
                assert!(msg.contains("refrigerant_id not set"));
            }
            _ => panic!("Expected InvalidState error"),
        }
    }

    #[test]
    fn test_validate_outlet_quality_no_backend() {
        let evap = FloodedEvaporator::new(10_000.0).with_refrigerant("R134a");
        let result = evap.validate_outlet_quality(400_000.0, 300_000.0);
        assert!(result.is_err());
        match result {
            Err(ComponentError::CalculationFailed(msg)) => {
                assert!(msg.contains("FluidBackend not configured"));
            }
            _ => panic!("Expected CalculationFailed error"),
        }
    }

    #[test]
    fn test_set_outlet_indices() {
        let mut evap = FloodedEvaporator::new(10_000.0).with_quality_control(true);
        evap.set_outlet_indices(2, 3);

        let state = vec![0.0, 0.0, 300_000.0, 400_000.0, 0.0, 0.0];
        let mut residuals = vec![0.0; 3];
        let result = evap.compute_residuals(&state, &mut residuals);
        assert!(result.is_ok());
    }

    #[test]
    fn test_heat_transfer_initial_value() {
        let evap = FloodedEvaporator::new(10_000.0);
        assert_eq!(evap.heat_transfer(), 0.0);
    }
}
