//! Types for fluid property calculations.
//!
//! This module defines the core types used to represent thermodynamic states,
//! fluid identifiers, and properties in the fluid backend system.

use crate::mixture::Mixture;
pub use entropyk_core::Entropy;
use entropyk_core::{Enthalpy, Pressure, Temperature};
use std::fmt;

/// Difference between two temperatures in Kelvin.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct TemperatureDelta(pub f64);

impl TemperatureDelta {
    /// Creates a new TemperatureDelta from a difference in Kelvin.
    pub fn new(kelvin_diff: f64) -> Self {
        TemperatureDelta(kelvin_diff)
    }

    /// Gets the temperature difference in Kelvin.
    pub fn kelvin(&self) -> f64 {
        self.0
    }
}

impl From<f64> for TemperatureDelta {
    fn from(val: f64) -> Self {
        TemperatureDelta(val)
    }
}

/// Unique identifier for a fluid (e.g., "R410A", "Water", "Air").
#[derive(
    Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub struct FluidId(pub String);

impl FluidId {
    /// Creates a new FluidId from a string.
    pub fn new(name: impl Into<String>) -> Self {
        FluidId(name.into())
    }

    /// Returns the fluid name as a string slice.
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Consumes the FluidId and returns the inner string.
    pub fn into_inner(self) -> String {
        self.0
    }
}

impl fmt::Display for FluidId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl AsRef<str> for FluidId {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl From<&str> for FluidId {
    fn from(s: &str) -> Self {
        FluidId(s.to_string())
    }
}

impl From<String> for FluidId {
    fn from(s: String) -> Self {
        FluidId(s)
    }
}

/// Thermodynamic property that can be queried from a backend.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Property {
    /// Density (kg/m³)
    Density,
    /// Specific enthalpy (J/kg)
    Enthalpy,
    /// Specific entropy (J/(kg·K))
    Entropy,
    /// Specific internal energy (J/kg)
    InternalEnergy,
    /// Specific heat capacity at constant pressure (J/(kg·K))
    Cp,
    /// Specific heat capacity at constant volume (J/(kg·K))
    Cv,
    /// Speed of sound (m/s)
    SpeedOfSound,
    /// Dynamic viscosity (Pa·s)
    Viscosity,
    /// Thermal conductivity (W/(m·K))
    ThermalConductivity,
    /// Surface tension (N/m)
    SurfaceTension,
    /// Quality (0-1 for two-phase)
    Quality,
    /// Temperature (K)
    Temperature,
    /// Pressure (Pa)
    Pressure,
}

impl fmt::Display for Property {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Property::Density => write!(f, "Density"),
            Property::Enthalpy => write!(f, "Enthalpy"),
            Property::Entropy => write!(f, "Entropy"),
            Property::InternalEnergy => write!(f, "InternalEnergy"),
            Property::Cp => write!(f, "Cp"),
            Property::Cv => write!(f, "Cv"),
            Property::SpeedOfSound => write!(f, "SpeedOfSound"),
            Property::Viscosity => write!(f, "Viscosity"),
            Property::ThermalConductivity => write!(f, "ThermalConductivity"),
            Property::SurfaceTension => write!(f, "SurfaceTension"),
            Property::Quality => write!(f, "Quality"),
            Property::Temperature => write!(f, "Temperature"),
            Property::Pressure => write!(f, "Pressure"),
        }
    }
}

/// Input specification for thermodynamic state calculation.
///
/// Defines what inputs are available to look up a thermodynamic property.
#[derive(Debug, Clone, PartialEq)]
pub enum FluidState {
    /// Pressure and temperature (P, T) - most common for single-phase
    PressureTemperature(Pressure, Temperature),
    /// Pressure and enthalpy (P, h) - common for expansion/compression
    PressureEnthalpy(Pressure, Enthalpy),
    /// Pressure and entropy (P, s) - useful for isentropic processes
    PressureEntropy(Pressure, Entropy),
    /// Pressure and quality (P, x) - for two-phase regions
    PressureQuality(Pressure, Quality),
    /// Pressure and temperature with mixture (P, T, mixture)
    PressureTemperatureMixture(Pressure, Temperature, Mixture),
    /// Pressure and enthalpy with mixture (P, h, mixture) - preferred for two-phase
    PressureEnthalpyMixture(Pressure, Enthalpy, Mixture),
    /// Pressure and quality with mixture (P, x, mixture) - for two-phase mixtures
    PressureQualityMixture(Pressure, Quality, Mixture),
}

impl FluidState {
    /// Creates a state from pressure and temperature.
    pub fn from_pt(p: Pressure, t: Temperature) -> Self {
        FluidState::PressureTemperature(p, t)
    }

    /// Creates a state from pressure and enthalpy.
    pub fn from_ph(p: Pressure, h: Enthalpy) -> Self {
        FluidState::PressureEnthalpy(p, h)
    }

    /// Creates a state from pressure and entropy.
    pub fn from_ps(p: Pressure, s: Entropy) -> Self {
        FluidState::PressureEntropy(p, s)
    }

    /// Creates a state from pressure and quality.
    pub fn from_px(p: Pressure, x: Quality) -> Self {
        FluidState::PressureQuality(p, x)
    }

    /// Creates a state from pressure, temperature, and mixture.
    pub fn from_pt_mix(p: Pressure, t: Temperature, mix: Mixture) -> Self {
        FluidState::PressureTemperatureMixture(p, t, mix)
    }

    /// Creates a state from pressure, enthalpy, and mixture (preferred for two-phase).
    pub fn from_ph_mix(p: Pressure, h: Enthalpy, mix: Mixture) -> Self {
        FluidState::PressureEnthalpyMixture(p, h, mix)
    }

    /// Creates a state from pressure, quality, and mixture.
    pub fn from_px_mix(p: Pressure, x: Quality, mix: Mixture) -> Self {
        FluidState::PressureQualityMixture(p, x, mix)
    }

    /// Check if this state contains a mixture.
    pub fn is_mixture(&self) -> bool {
        matches!(
            self,
            FluidState::PressureTemperatureMixture(_, _, _)
                | FluidState::PressureEnthalpyMixture(_, _, _)
                | FluidState::PressureQualityMixture(_, _, _)
        )
    }
}

/// Quality (vapor fraction) from 0 (saturated liquid) to 1 (saturated vapor).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Quality(pub f64);

impl Quality {
    /// Creates a quality value (0-1).
    pub fn new(value: f64) -> Self {
        Quality(value.clamp(0.0, 1.0))
    }

    /// Returns the quality value (0-1).
    pub fn value(&self) -> f64 {
        self.0
    }
}

impl From<f64> for Quality {
    fn from(value: f64) -> Self {
        Quality::new(value)
    }
}

/// Critical point data for a fluid.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CriticalPoint {
    /// Critical temperature in Kelvin.
    pub temperature: Temperature,
    /// Critical pressure in Pascals.
    pub pressure: Pressure,
    /// Critical density in kg/m³.
    pub density: f64,
}

impl CriticalPoint {
    /// Creates a new CriticalPoint.
    pub fn new(temperature: Temperature, pressure: Pressure, density: f64) -> Self {
        CriticalPoint {
            temperature,
            pressure,
            density,
        }
    }

    /// Returns critical temperature in Kelvin.
    pub fn temperature_kelvin(&self) -> f64 {
        self.temperature.to_kelvin()
    }

    /// Returns critical pressure in Pascals.
    pub fn pressure_pascals(&self) -> f64 {
        self.pressure.to_pascals()
    }
}

/// Phase of matter.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Phase {
    /// Liquid phase.
    Liquid,
    /// Vapor/gas phase.
    Vapor,
    /// Two-phase region.
    TwoPhase,
    /// Supercritical fluid.
    Supercritical,
    /// Unknown or undefined phase.
    Unknown,
}

impl fmt::Display for Phase {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Phase::Liquid => write!(f, "Liquid"),
            Phase::Vapor => write!(f, "Vapor"),
            Phase::TwoPhase => write!(f, "TwoPhase"),
            Phase::Supercritical => write!(f, "Supercritical"),
            Phase::Unknown => write!(f, "Unknown"),
        }
    }
}

/// Comprehensive representation of a thermodynamic state.
///
/// This struct holds a complete snapshot of all relevant properties for a fluid
/// at a given state. It avoids the need to repeatedly query the backend for
/// individual properties once the state is resolved.
#[derive(Debug, Clone, PartialEq)]
pub struct ThermoState {
    /// Fluid identifier (e.g. "R410A")
    pub fluid: FluidId,
    /// Absolute pressure
    pub pressure: Pressure,
    /// Absolute temperature
    pub temperature: Temperature,
    /// Specific enthalpy
    pub enthalpy: Enthalpy,
    /// Specific entropy
    pub entropy: Entropy,
    /// Density in kg/m³
    pub density: f64,
    /// Physical phase of the fluid
    pub phase: Phase,
    /// Vapor quality (0.0 to 1.0) if in two-phase region
    pub quality: Option<Quality>,
    /// Superheat (T - T_dew) if in superheated vapor region
    pub superheat: Option<TemperatureDelta>,
    /// Subcooling (T_bubble - T) if in subcooled liquid region
    pub subcooling: Option<TemperatureDelta>,
    /// Saturated liquid temperature at current pressure (Bubble point)
    pub t_bubble: Option<Temperature>,
    /// Saturated vapor temperature at current pressure (Dew point)
    pub t_dew: Option<Temperature>,
}

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

    #[test]
    fn test_new() {
        let id = FluidId::new("R134a");
        assert_eq!(id.0, "R134a");
    }

    #[test]
    fn test_from_str() {
        let id: FluidId = "R410A".into();
        assert_eq!(id.0, "R410A");
    }

    #[test]
    fn test_from_string() {
        let id: FluidId = String::from("R744").into();
        assert_eq!(id.0, "R744");
    }

    #[test]
    fn test_as_str() {
        let id = FluidId::new("Water");
        assert_eq!(id.as_str(), "Water");
    }

    #[test]
    fn test_into_inner() {
        let id = FluidId::new("Air");
        let inner = id.into_inner();
        assert_eq!(inner, "Air");
    }

    #[test]
    fn test_as_ref() {
        let id = FluidId::new("R1234yf");
        let s: &str = id.as_ref();
        assert_eq!(s, "R1234yf");
    }

    #[test]
    fn test_display() {
        let id = FluidId::new("R32");
        assert_eq!(format!("{}", id), "R32");
    }

    #[test]
    fn test_fluid_state_from_pt() {
        let p = Pressure::from_bar(1.0);
        let t = Temperature::from_celsius(25.0);
        let state = FluidState::from_pt(p, t);
        match state {
            FluidState::PressureTemperature(p_out, t_out) => {
                assert_eq!(p.to_pascals(), p_out.to_pascals());
                assert_eq!(t.to_kelvin(), t_out.to_kelvin());
            }
            _ => panic!("Expected PressureTemperature variant"),
        }
    }

    #[test]
    fn test_quality_bounds() {
        let q1 = Quality::new(0.5);
        assert!((q1.value() - 0.5).abs() < 1e-10);

        let q2 = Quality::new(1.5);
        assert!((q2.value() - 1.0).abs() < 1e-10);

        let q3 = Quality::new(-0.5);
        assert!((q3.value() - 0.0).abs() < 1e-10);
    }

    #[test]
    fn test_critical_point() {
        // CO2 critical point: Tc = 304.13 K, Pc = 7.3773 MPa
        let cp = CriticalPoint::new(
            Temperature::from_kelvin(304.13),
            Pressure::from_pascals(7.3773e6),
            467.0,
        );
        assert!((cp.temperature_kelvin() - 304.13).abs() < 0.01);
        assert!((cp.pressure_pascals() - 7.3773e6).abs() < 1.0);
    }

    #[test]
    fn test_property_display() {
        assert_eq!(format!("{}", Property::Density), "Density");
        assert_eq!(format!("{}", Property::Enthalpy), "Enthalpy");
    }
}
