//! Python wrappers for Entropyk core physical types.
//!
//! Each wrapper holds the inner Rust NewType and exposes Pythonic constructors
//! with keyword arguments (e.g., `Pressure(bar=1.0)`) plus unit conversion methods.

use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;

// =============================================================================
// Pressure
// =============================================================================

/// Pressure in Pascals (Pa).
///
/// Construct with one of: ``pa``, ``bar``, ``kpa``, ``psi``.
///
/// Example::
///
///     p = Pressure(bar=1.0)
///     print(p.to_bar())   # 1.0
///     print(float(p))     # 100000.0
#[pyclass(name = "Pressure", module = "entropyk")]
#[derive(Clone)]
pub struct PyPressure {
    pub(crate) inner: entropyk::Pressure,
}

#[pymethods]
impl PyPressure {
    /// Create a Pressure. Specify exactly one of: ``pa``, ``bar``, ``kpa``, ``psi``.
    #[new]
    #[pyo3(signature = (pa=None, bar=None, kpa=None, psi=None))]
    fn new(
        pa: Option<f64>,
        bar: Option<f64>,
        kpa: Option<f64>,
        psi: Option<f64>,
    ) -> PyResult<Self> {
        let value = match (pa, bar, kpa, psi) {
            (Some(v), None, None, None) => v,
            (None, Some(v), None, None) => v * 100_000.0,
            (None, None, Some(v), None) => v * 1_000.0,
            (None, None, None, Some(v)) => v * 6894.75729,
            _ => {
                return Err(PyValueError::new_err(
                    "Specify exactly one of: pa, bar, kpa, psi",
                ))
            }
        };
        Ok(PyPressure {
            inner: entropyk::Pressure(value),
        })
    }

    /// Value in Pascals.
    fn to_pascals(&self) -> f64 {
        self.inner.to_pascals()
    }

    /// Value in bar.
    fn to_bar(&self) -> f64 {
        self.inner.to_bar()
    }

    /// Value in kPa.
    fn to_kpa(&self) -> f64 {
        self.inner.0 / 1_000.0
    }

    /// Value in PSI.
    fn to_psi(&self) -> f64 {
        self.inner.to_psi()
    }

    fn __repr__(&self) -> String {
        format!(
            "Pressure({:.2} Pa = {:.4} bar)",
            self.inner.0,
            self.inner.0 / 100_000.0
        )
    }

    fn __str__(&self) -> String {
        format!("{:.2} Pa", self.inner.0)
    }

    fn __float__(&self) -> f64 {
        self.inner.0
    }

    fn __eq__(&self, other: &PyPressure) -> bool {
        (self.inner.0 - other.inner.0).abs() < 1e-10
    }

    fn __add__(&self, other: &PyPressure) -> PyPressure {
        PyPressure {
            inner: self.inner + other.inner,
        }
    }

    fn __sub__(&self, other: &PyPressure) -> PyPressure {
        PyPressure {
            inner: self.inner - other.inner,
        }
    }
}

// =============================================================================
// Temperature
// =============================================================================

/// Temperature in Kelvin (K).
///
/// Construct with one of: ``kelvin``, ``celsius``, ``fahrenheit``.
///
/// Example::
///
///     t = Temperature(celsius=25.0)
///     print(t.to_kelvin())     # 298.15
///     print(t.to_celsius())    # 25.0
#[pyclass(name = "Temperature", module = "entropyk")]
#[derive(Clone)]
pub struct PyTemperature {
    pub(crate) inner: entropyk::Temperature,
}

#[pymethods]
impl PyTemperature {
    /// Create a Temperature. Specify exactly one of: ``kelvin``, ``celsius``, ``fahrenheit``.
    #[new]
    #[pyo3(signature = (kelvin=None, celsius=None, fahrenheit=None))]
    fn new(kelvin: Option<f64>, celsius: Option<f64>, fahrenheit: Option<f64>) -> PyResult<Self> {
        let inner = match (kelvin, celsius, fahrenheit) {
            (Some(v), None, None) => entropyk::Temperature::from_kelvin(v),
            (None, Some(v), None) => entropyk::Temperature::from_celsius(v),
            (None, None, Some(v)) => entropyk::Temperature::from_fahrenheit(v),
            _ => {
                return Err(PyValueError::new_err(
                    "Specify exactly one of: kelvin, celsius, fahrenheit",
                ))
            }
        };
        Ok(PyTemperature { inner })
    }

    /// Value in Kelvin.
    fn to_kelvin(&self) -> f64 {
        self.inner.to_kelvin()
    }

    /// Value in Celsius.
    fn to_celsius(&self) -> f64 {
        self.inner.to_celsius()
    }

    /// Value in Fahrenheit.
    fn to_fahrenheit(&self) -> f64 {
        self.inner.to_fahrenheit()
    }

    fn __repr__(&self) -> String {
        format!(
            "Temperature({:.2} K = {:.2} °C)",
            self.inner.0,
            self.inner.0 - 273.15
        )
    }

    fn __str__(&self) -> String {
        format!("{:.2} K", self.inner.0)
    }

    fn __float__(&self) -> f64 {
        self.inner.0
    }

    fn __eq__(&self, other: &PyTemperature) -> bool {
        (self.inner.0 - other.inner.0).abs() < 1e-10
    }

    fn __add__(&self, other: &PyTemperature) -> PyTemperature {
        PyTemperature {
            inner: entropyk::Temperature(self.inner.0 + other.inner.0),
        }
    }

    fn __sub__(&self, other: &PyTemperature) -> PyTemperature {
        PyTemperature {
            inner: entropyk::Temperature(self.inner.0 - other.inner.0),
        }
    }
}

// =============================================================================
// Enthalpy
// =============================================================================

/// Specific enthalpy in J/kg.
///
/// Construct with one of: ``j_per_kg``, ``kj_per_kg``.
///
/// Example::
///
///     h = Enthalpy(kj_per_kg=250.0)
///     print(h.to_kj_per_kg())  # 250.0
#[pyclass(name = "Enthalpy", module = "entropyk")]
#[derive(Clone)]
pub struct PyEnthalpy {
    pub(crate) inner: entropyk::Enthalpy,
}

#[pymethods]
impl PyEnthalpy {
    /// Create an Enthalpy. Specify exactly one of: ``j_per_kg``, ``kj_per_kg``.
    #[new]
    #[pyo3(signature = (j_per_kg=None, kj_per_kg=None))]
    fn new(j_per_kg: Option<f64>, kj_per_kg: Option<f64>) -> PyResult<Self> {
        let inner = match (j_per_kg, kj_per_kg) {
            (Some(v), None) => entropyk::Enthalpy::from_joules_per_kg(v),
            (None, Some(v)) => entropyk::Enthalpy::from_kilojoules_per_kg(v),
            _ => {
                return Err(PyValueError::new_err(
                    "Specify exactly one of: j_per_kg, kj_per_kg",
                ))
            }
        };
        Ok(PyEnthalpy { inner })
    }

    /// Value in J/kg.
    fn to_j_per_kg(&self) -> f64 {
        self.inner.to_joules_per_kg()
    }

    /// Value in kJ/kg.
    fn to_kj_per_kg(&self) -> f64 {
        self.inner.to_kilojoules_per_kg()
    }

    fn __repr__(&self) -> String {
        format!(
            "Enthalpy({:.2} J/kg = {:.2} kJ/kg)",
            self.inner.0,
            self.inner.0 / 1_000.0
        )
    }

    fn __str__(&self) -> String {
        format!("{:.2} J/kg", self.inner.0)
    }

    fn __float__(&self) -> f64 {
        self.inner.0
    }

    fn __eq__(&self, other: &PyEnthalpy) -> bool {
        (self.inner.0 - other.inner.0).abs() < 1e-10
    }

    fn __add__(&self, other: &PyEnthalpy) -> PyEnthalpy {
        PyEnthalpy {
            inner: entropyk::Enthalpy(self.inner.0 + other.inner.0),
        }
    }

    fn __sub__(&self, other: &PyEnthalpy) -> PyEnthalpy {
        PyEnthalpy {
            inner: entropyk::Enthalpy(self.inner.0 - other.inner.0),
        }
    }
}

// =============================================================================
// MassFlow
// =============================================================================

/// Mass flow rate in kg/s.
///
/// Construct with one of: ``kg_per_s``, ``g_per_s``.
///
/// Example::
///
///     m = MassFlow(kg_per_s=0.5)
///     print(m.to_g_per_s())  # 500.0
#[pyclass(name = "MassFlow", module = "entropyk")]
#[derive(Clone)]
pub struct PyMassFlow {
    pub(crate) inner: entropyk::MassFlow,
}

#[pymethods]
impl PyMassFlow {
    /// Create a MassFlow. Specify exactly one of: ``kg_per_s``, ``g_per_s``.
    #[new]
    #[pyo3(signature = (kg_per_s=None, g_per_s=None))]
    fn new(kg_per_s: Option<f64>, g_per_s: Option<f64>) -> PyResult<Self> {
        let inner = match (kg_per_s, g_per_s) {
            (Some(v), None) => entropyk::MassFlow::from_kg_per_s(v),
            (None, Some(v)) => entropyk::MassFlow::from_grams_per_s(v),
            _ => {
                return Err(PyValueError::new_err(
                    "Specify exactly one of: kg_per_s, g_per_s",
                ))
            }
        };
        Ok(PyMassFlow { inner })
    }

    /// Value in kg/s.
    fn to_kg_per_s(&self) -> f64 {
        self.inner.to_kg_per_s()
    }

    /// Value in g/s.
    fn to_g_per_s(&self) -> f64 {
        self.inner.to_grams_per_s()
    }

    fn __repr__(&self) -> String {
        format!("MassFlow({:.6} kg/s)", self.inner.0)
    }

    fn __str__(&self) -> String {
        format!("{:.6} kg/s", self.inner.0)
    }

    fn __float__(&self) -> f64 {
        self.inner.0
    }

    fn __eq__(&self, other: &PyMassFlow) -> bool {
        (self.inner.0 - other.inner.0).abs() < 1e-15
    }

    fn __add__(&self, other: &PyMassFlow) -> PyMassFlow {
        PyMassFlow {
            inner: entropyk::MassFlow(self.inner.0 + other.inner.0),
        }
    }

    fn __sub__(&self, other: &PyMassFlow) -> PyMassFlow {
        PyMassFlow {
            inner: entropyk::MassFlow(self.inner.0 - other.inner.0),
        }
    }
}

// =============================================================================
// Concentration
// =============================================================================

#[pyclass(name = "Concentration", module = "entropyk")]
#[derive(Clone)]
pub struct PyConcentration {
    pub(crate) inner: entropyk::Concentration,
}

#[pymethods]
impl PyConcentration {
    #[new]
    fn new(value: f64) -> PyResult<Self> {
        if !(0.0..=1.0).contains(&value) {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "Value must be between 0.0 and 1.0",
            ));
        }
        Ok(PyConcentration {
            inner: entropyk::Concentration::from_fraction(value),
        })
    }

    #[getter]
    fn value(&self) -> f64 {
        self.inner.to_fraction()
    }

    #[staticmethod]
    fn from_percent(value: f64) -> Self {
        PyConcentration {
            inner: entropyk::Concentration::from_percent(value),
        }
    }

    #[staticmethod]
    fn from_fraction(value: f64) -> Self {
        PyConcentration {
            inner: entropyk::Concentration::from_fraction(value),
        }
    }

    fn to_percent(&self) -> f64 {
        self.inner.to_percent()
    }

    fn to_fraction(&self) -> f64 {
        self.inner.to_fraction()
    }

    fn to_mass_fraction(&self) -> f64 {
        self.inner.to_fraction()
    }

    fn __repr__(&self) -> String {
        format!("Concentration({:.2}%)", self.inner.to_percent())
    }

    fn __float__(&self) -> f64 {
        self.inner.to_fraction()
    }

    fn __eq__(&self, other: &PyConcentration) -> bool {
        (self.inner.0 - other.inner.0).abs() < 1e-10
    }
}

// =============================================================================
// VolumeFlow
// =============================================================================

#[pyclass(name = "VolumeFlow", module = "entropyk")]
#[derive(Clone)]
pub struct PyVolumeFlow {
    pub(crate) inner: entropyk::VolumeFlow,
}

#[pymethods]
impl PyVolumeFlow {
    #[new]
    fn new(value: f64) -> PyResult<Self> {
        if value < 0.0 {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "Value cannot be negative",
            ));
        }
        Ok(PyVolumeFlow {
            inner: entropyk::VolumeFlow::from_m3_per_s(value),
        })
    }

    #[getter]
    fn value(&self) -> f64 {
        self.inner.to_m3_per_s()
    }

    #[staticmethod]
    fn from_m3_per_s(value: f64) -> Self {
        PyVolumeFlow {
            inner: entropyk::VolumeFlow::from_m3_per_s(value),
        }
    }

    #[staticmethod]
    fn from_l_per_min(value: f64) -> Self {
        PyVolumeFlow {
            inner: entropyk::VolumeFlow::from_l_per_min(value),
        }
    }

    fn to_m3_per_s(&self) -> f64 {
        self.inner.to_m3_per_s()
    }

    fn to_l_per_min(&self) -> f64 {
        self.inner.to_l_per_min()
    }

    fn __repr__(&self) -> String {
        format!("VolumeFlow({:.6} m³/s)", self.inner.to_m3_per_s())
    }

    fn __float__(&self) -> f64 {
        self.inner.to_m3_per_s()
    }

    fn __eq__(&self, other: &PyVolumeFlow) -> bool {
        (self.inner.0 - other.inner.0).abs() < 1e-15
    }
}

// =============================================================================
// RelativeHumidity
// =============================================================================

#[pyclass(name = "RelativeHumidity", module = "entropyk")]
#[derive(Clone)]
pub struct PyRelativeHumidity {
    pub(crate) inner: entropyk::RelativeHumidity,
}

#[pymethods]
impl PyRelativeHumidity {
    #[new]
    fn new(value: f64) -> PyResult<Self> {
        if !(0.0..=1.0).contains(&value) {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "Value must be between 0.0 and 1.0",
            ));
        }
        Ok(PyRelativeHumidity {
            inner: entropyk::RelativeHumidity::from_fraction(value),
        })
    }

    #[getter]
    fn value(&self) -> f64 {
        self.inner.to_fraction()
    }

    #[staticmethod]
    fn from_percent(value: f64) -> Self {
        PyRelativeHumidity {
            inner: entropyk::RelativeHumidity::from_percent(value),
        }
    }

    #[staticmethod]
    fn from_fraction(value: f64) -> Self {
        PyRelativeHumidity {
            inner: entropyk::RelativeHumidity::from_fraction(value),
        }
    }

    fn to_percent(&self) -> f64 {
        self.inner.to_percent()
    }

    fn to_fraction(&self) -> f64 {
        self.inner.to_fraction()
    }

    fn __repr__(&self) -> String {
        format!("RelativeHumidity({:.2}%)", self.inner.to_percent())
    }

    fn __float__(&self) -> f64 {
        self.inner.to_fraction()
    }

    fn __eq__(&self, other: &PyRelativeHumidity) -> bool {
        (self.inner.0 - other.inner.0).abs() < 1e-10
    }
}

// =============================================================================
// VaporQuality
// =============================================================================

#[pyclass(name = "VaporQuality", module = "entropyk")]
#[derive(Clone)]
pub struct PyVaporQuality {
    pub(crate) inner: entropyk::VaporQuality,
}

#[pymethods]
impl PyVaporQuality {
    #[new]
    fn new(value: f64) -> PyResult<Self> {
        if !(0.0..=1.0).contains(&value) {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "Value must be between 0.0 and 1.0",
            ));
        }
        Ok(PyVaporQuality {
            inner: entropyk::VaporQuality::from_fraction(value),
        })
    }

    #[getter]
    fn value(&self) -> f64 {
        self.inner.to_fraction()
    }

    #[staticmethod]
    fn from_fraction(value: f64) -> Self {
        PyVaporQuality {
            inner: entropyk::VaporQuality::from_fraction(value),
        }
    }

    #[staticmethod]
    fn from_percent(value: f64) -> Self {
        PyVaporQuality {
            inner: entropyk::VaporQuality::from_percent(value),
        }
    }

    fn to_fraction(&self) -> f64 {
        self.inner.to_fraction()
    }

    fn to_percent(&self) -> f64 {
        self.inner.to_percent()
    }

    fn __repr__(&self) -> String {
        format!("VaporQuality({:.2}%)", self.inner.to_percent())
    }

    fn __float__(&self) -> f64 {
        self.inner.to_fraction()
    }

    fn __eq__(&self, other: &PyVaporQuality) -> bool {
        (self.inner.0 - other.inner.0).abs() < 1e-10
    }
}
