//! Fluid backend trait and implementations.
//!
//! This module defines the core `FluidBackend` trait that abstracts the source
//! of thermodynamic property data, allowing the solver to switch between different
//! backends (CoolProp, tabular data, mock for testing).

use crate::errors::FluidResult;
use crate::mixture::Mixture;
use crate::types::{CriticalPoint, FluidId, FluidState, Phase, Property, ThermoState};
use entropyk_core::{Pressure, Temperature};

/// Trait for fluid property backends.
///
/// Implementors must provide methods to query thermodynamic properties
/// for various fluids. This allows the solver to work with different
/// property sources (CoolProp, tabular data, mock data for testing).
///
/// # Example
///
/// ```
/// use entropyk_fluids::{FluidBackend, FluidId, Property, FluidState, ThermoState, FluidError, FluidResult, CriticalPoint};
///
/// struct MyBackend;
/// impl FluidBackend for MyBackend {
///     fn property(&self, _fluid: FluidId, _property: Property, _state: FluidState) -> FluidResult<f64> {
///         Ok(1.0)
///     }
///     fn critical_point(&self, fluid: FluidId) -> FluidResult<CriticalPoint> {
///         Err(FluidError::NoCriticalPoint { fluid: fluid.0 })
///     }
///     fn is_fluid_available(&self, _fluid: &FluidId) -> bool { false }
///     fn phase(&self, _fluid: FluidId, _state: FluidState) -> FluidResult<entropyk_fluids::Phase> {
///         Ok(entropyk_fluids::Phase::Unknown)
///     }
///     fn full_state(&self, _fluid: FluidId, _p: entropyk_core::Pressure, _h: entropyk_core::Enthalpy) -> FluidResult<ThermoState> {
///         Err(FluidError::UnsupportedProperty { property: "full_state".to_string() })
///     }
///     fn list_fluids(&self) -> Vec<FluidId> { vec![] }
/// }
/// ```
pub trait FluidBackend: Send + Sync {
    /// Query a thermodynamic property for a fluid at a given state.
    ///
    /// # Arguments
    /// * `fluid` - The fluid identifier (e.g., "R134a", "CO2")
    /// * `property` - The property to query
    /// * `state` - The thermodynamic state specification
    ///
    /// # Returns
    /// The property value in SI units, or an error if the property
    /// cannot be computed (unknown fluid, invalid state, etc.)
    fn property(&self, fluid: FluidId, property: Property, state: FluidState) -> FluidResult<f64>;

    /// Compute the complete thermodynamic state of a fluid at a given pressure and enthalpy.
    ///
    /// This method is intended to be implemented by backends capable of natively calculating
    /// all key parameters (phase, saturation temperatures, qualities, limits) without the user
    /// needing to query them individually.
    ///
    /// # Arguments
    /// * `fluid` - The fluid identifier
    /// * `p` - The absolute pressure
    /// * `h` - The specific enthalpy
    ///
    /// # Returns
    /// The comprehensive `ThermoState` Snapshot, or an Error.
    fn full_state(
        &self,
        fluid: FluidId,
        p: Pressure,
        h: entropyk_core::Enthalpy,
    ) -> FluidResult<ThermoState>;

    /// Get critical point data for a fluid.
    ///
    /// # Arguments
    /// * `fluid` - The fluid identifier
    ///
    /// # Returns
    /// The critical point (Tc, Pc, density), or an error if not available
    fn critical_point(&self, fluid: FluidId) -> FluidResult<CriticalPoint>;

    /// Check if a fluid is available in this backend.
    ///
    /// # Arguments
    /// * `fluid` - The fluid identifier
    ///
    /// # Returns
    /// `true` if the fluid is available, `false` otherwise
    fn is_fluid_available(&self, fluid: &FluidId) -> bool;

    /// Get the phase of a fluid at a given state.
    ///
    /// # Arguments
    /// * `fluid` - The fluid identifier
    /// * `state` - The thermodynamic state
    ///
    /// # Returns
    /// The phase (Liquid, Vapor, TwoPhase, etc.)
    fn phase(&self, fluid: FluidId, state: FluidState) -> FluidResult<Phase>;

    /// List all available fluids in this backend.
    fn list_fluids(&self) -> Vec<FluidId>;

    /// Calculate the bubble point temperature for a mixture at given pressure.
    ///
    /// The bubble point is the temperature at which a liquid mixture begins to boil
    /// (saturated liquid temperature).
    ///
    /// # Arguments
    /// * `pressure` - The pressure in Pa
    /// * `mixture` - The mixture composition
    ///
    /// # Returns
    /// The bubble point temperature in Kelvin
    fn bubble_point(&self, _pressure: Pressure, _mixture: &Mixture) -> FluidResult<Temperature> {
        Err(crate::errors::FluidError::UnsupportedProperty {
            property: "Bubble point calculation not supported by this backend".to_string(),
        })
    }

    /// Calculate the dew point temperature for a mixture at given pressure.
    ///
    /// The dew point is the temperature at which a vapor mixture begins to condense
    /// (saturated vapor temperature).
    ///
    /// # Arguments
    /// * `pressure` - The pressure in Pa
    /// * `mixture` - The mixture composition
    ///
    /// # Returns
    /// The dew point temperature in Kelvin
    fn dew_point(&self, _pressure: Pressure, _mixture: &Mixture) -> FluidResult<Temperature> {
        Err(crate::errors::FluidError::UnsupportedProperty {
            property: "Dew point calculation not supported by this backend".to_string(),
        })
    }

    /// Calculate the temperature glide for a mixture at given pressure.
    ///
    /// Temperature glide is the difference between dew point and bubble point
    /// temperatures: T_glide = T_dew - T_bubble.
    /// This is non-zero for zeotropic mixtures and zero for azeotropes/pure fluids.
    ///
    /// # Arguments
    /// * `pressure` - The pressure in Pa
    /// * `mixture` - The mixture composition
    ///
    /// # Returns
    /// The temperature glide in Kelvin
    fn temperature_glide(&self, pressure: Pressure, mixture: &Mixture) -> FluidResult<f64> {
        let t_bubble = self.bubble_point(pressure, mixture)?;
        let t_dew = self.dew_point(pressure, mixture)?;
        Ok(t_dew.to_kelvin() - t_bubble.to_kelvin())
    }

    /// Check if a mixture is supported by this backend.
    ///
    /// # Arguments
    /// * `mixture` - The mixture to check
    ///
    /// # Returns
    /// `true` if the mixture is supported, `false` otherwise
    fn is_mixture_supported(&self, mixture: &Mixture) -> bool {
        // Default implementation: check if all components are available
        mixture
            .components()
            .iter()
            .all(|c| self.is_fluid_available(&FluidId::new(c)))
    }

    /// Get the saturation pressure for a pure refrigerant at a given temperature.
    ///
    /// Equivalent to `PropsSI("P", "T", t_k, "Q", 0, fluid)`.
    ///
    /// # Arguments
    /// * `fluid` - The fluid identifier (e.g., "R410A")
    /// * `t_k` - Saturation temperature in Kelvin
    ///
    /// # Returns
    /// Saturation pressure in Pa, or an error if not supported by this backend.
    fn saturation_pressure_t(&self, _fluid: FluidId, _t_k: f64) -> FluidResult<f64> {
        Err(crate::errors::FluidError::UnsupportedProperty {
            property: "saturation_pressure_t (T,Q query) not supported by this backend".to_string(),
        })
    }

    /// Get the specific enthalpy for a pure refrigerant at a given temperature and quality.
    ///
    /// Equivalent to `PropsSI("H", "T", t_k, "Q", quality, fluid)`.
    ///
    /// # Arguments
    /// * `fluid` - The fluid identifier
    /// * `t_k` - Temperature in Kelvin
    /// * `quality` - Vapor quality (0.0 = saturated liquid, 1.0 = saturated vapor)
    ///
    /// # Returns
    /// Specific enthalpy in J/kg, or an error if not supported by this backend.
    fn saturation_enthalpy_t(&self, _fluid: FluidId, _t_k: f64, _quality: f64) -> FluidResult<f64> {
        Err(crate::errors::FluidError::UnsupportedProperty {
            property: "saturation_enthalpy_t (T,Q query) not supported by this backend".to_string(),
        })
    }
}
