//! WASM type wrappers for core physical types.
//!
//! Provides JavaScript-friendly wrappers for Pressure, Temperature,
//! Enthalpy, and MassFlow with JSON serialization support.

use entropyk_core::{Enthalpy, MassFlow, Pressure, Temperature};
use serde::Serialize;
use wasm_bindgen::prelude::*;

/// Pressure in Pascals.
#[wasm_bindgen]
#[derive(Clone, Copy, Debug, Serialize)]
pub struct WasmPressure {
    pascals: f64,
}

#[wasm_bindgen]
impl WasmPressure {
    /// Create pressure from Pascals. Rejects NaN and negative values.
    #[wasm_bindgen(constructor)]
    pub fn new(pascals: f64) -> Result<WasmPressure, JsValue> {
        if pascals.is_nan() {
            return Err(js_sys::Error::new("Pressure cannot be NaN").into());
        }
        if pascals < 0.0 {
            return Err(js_sys::Error::new("Pressure cannot be negative").into());
        }
        Ok(WasmPressure { pascals })
    }

    /// Create pressure from bar. Rejects NaN and negative values.
    pub fn from_bar(bar: f64) -> Result<Self, JsValue> {
        if bar.is_nan() {
            return Err(js_sys::Error::new("Pressure cannot be NaN").into());
        }
        if bar < 0.0 {
            return Err(js_sys::Error::new("Pressure cannot be negative").into());
        }
        Ok(WasmPressure {
            pascals: bar * 100_000.0,
        })
    }

    /// Get pressure in Pascals.
    pub fn pascals(&self) -> f64 {
        self.pascals
    }

    /// Get pressure in bar.
    pub fn bar(&self) -> f64 {
        self.pascals / 100_000.0
    }

    /// Convert to JSON string.
    pub fn toJson(&self) -> Result<String, JsValue> {
        serde_json::to_string(self).map_err(|e| js_sys::Error::new(&e.to_string()).into())
    }
}

impl From<Pressure> for WasmPressure {
    fn from(p: Pressure) -> Self {
        WasmPressure {
            pascals: p.to_pascals(),
        }
    }
}

impl From<WasmPressure> for Pressure {
    fn from(p: WasmPressure) -> Self {
        Pressure::from_pascals(p.pascals)
    }
}

/// Temperature in Kelvin.
#[wasm_bindgen]
#[derive(Clone, Copy, Debug, Serialize)]
pub struct WasmTemperature {
    kelvin: f64,
}

#[wasm_bindgen]
impl WasmTemperature {
    /// Create temperature from Kelvin. Rejects NaN and negative values.
    #[wasm_bindgen(constructor)]
    pub fn new(kelvin: f64) -> Result<Self, JsValue> {
        if kelvin.is_nan() {
            return Err(js_sys::Error::new("Temperature cannot be NaN").into());
        }
        if kelvin < 0.0 {
            return Err(js_sys::Error::new("Temperature cannot be negative (Kelvin)").into());
        }
        Ok(WasmTemperature { kelvin })
    }

    /// Create temperature from Celsius.
    pub fn from_celsius(celsius: f64) -> Result<Self, JsValue> {
        Self::new(celsius + 273.15)
    }

    /// Get temperature in Kelvin.
    pub fn kelvin(&self) -> f64 {
        self.kelvin
    }

    /// Get temperature in Celsius.
    pub fn celsius(&self) -> f64 {
        self.kelvin - 273.15
    }

    /// Convert to JSON string.
    pub fn toJson(&self) -> Result<String, JsValue> {
        serde_json::to_string(self).map_err(|e| js_sys::Error::new(&e.to_string()).into())
    }
}

impl From<Temperature> for WasmTemperature {
    fn from(t: Temperature) -> Self {
        WasmTemperature {
            kelvin: t.to_kelvin(),
        }
    }
}

impl From<WasmTemperature> for Temperature {
    fn from(t: WasmTemperature) -> Self {
        Temperature::from_kelvin(t.kelvin)
    }
}

/// Enthalpy in J/kg.
#[wasm_bindgen]
#[derive(Clone, Copy, Debug, Serialize)]
pub struct WasmEnthalpy {
    joules_per_kg: f64,
}

#[wasm_bindgen]
impl WasmEnthalpy {
    /// Create enthalpy from J/kg. Rejects NaN.
    #[wasm_bindgen(constructor)]
    pub fn new(joules_per_kg: f64) -> Result<Self, JsValue> {
        if joules_per_kg.is_nan() {
            return Err(js_sys::Error::new("Enthalpy cannot be NaN").into());
        }
        Ok(WasmEnthalpy { joules_per_kg })
    }

    /// Create enthalpy from kJ/kg.
    pub fn from_kj_per_kg(kj_per_kg: f64) -> Result<Self, JsValue> {
        Self::new(kj_per_kg * 1000.0)
    }

    /// Get enthalpy in J/kg.
    pub fn joules_per_kg(&self) -> f64 {
        self.joules_per_kg
    }

    /// Get enthalpy in kJ/kg.
    pub fn kj_per_kg(&self) -> f64 {
        self.joules_per_kg / 1000.0
    }

    /// Convert to JSON string.
    pub fn toJson(&self) -> Result<String, JsValue> {
        serde_json::to_string(self).map_err(|e| js_sys::Error::new(&e.to_string()).into())
    }
}

impl From<Enthalpy> for WasmEnthalpy {
    fn from(h: Enthalpy) -> Self {
        WasmEnthalpy {
            joules_per_kg: h.to_joules_per_kg(),
        }
    }
}

impl From<WasmEnthalpy> for Enthalpy {
    fn from(h: WasmEnthalpy) -> Self {
        Enthalpy::from_joules_per_kg(h.joules_per_kg)
    }
}

/// Mass flow in kg/s.
#[wasm_bindgen]
#[derive(Clone, Copy, Debug, Serialize)]
pub struct WasmMassFlow {
    kg_per_s: f64,
}

#[wasm_bindgen]
impl WasmMassFlow {
    /// Create mass flow from kg/s. Rejects NaN.
    #[wasm_bindgen(constructor)]
    pub fn new(kg_per_s: f64) -> Result<Self, JsValue> {
        if kg_per_s.is_nan() {
            return Err(js_sys::Error::new("Mass flow cannot be NaN").into());
        }
        Ok(WasmMassFlow { kg_per_s })
    }

    /// Get mass flow in kg/s.
    pub fn kg_per_s(&self) -> f64 {
        self.kg_per_s
    }

    /// Convert to JSON string.
    pub fn toJson(&self) -> Result<String, JsValue> {
        serde_json::to_string(self).map_err(|e| js_sys::Error::new(&e.to_string()).into())
    }
}

impl From<MassFlow> for WasmMassFlow {
    fn from(m: MassFlow) -> Self {
        WasmMassFlow {
            kg_per_s: m.to_kg_per_s(),
        }
    }
}

impl From<WasmMassFlow> for MassFlow {
    fn from(m: WasmMassFlow) -> Self {
        MassFlow::from_kg_per_s(m.kg_per_s)
    }
}

/// WASM wrapper for thermodynamic state (result).
#[wasm_bindgen]
#[derive(Clone, Debug, Serialize)]
pub struct WasmThermoState {
    pub pressure: WasmPressure,
    pub temperature: WasmTemperature,
    pub enthalpy: WasmEnthalpy,
    /// Mass flow is not carried by ThermoState upstream; only populated
    /// when available from the solver context.
    pub mass_flow: WasmMassFlow,
}

#[wasm_bindgen]
impl WasmThermoState {
    /// Convert to JSON string.
    pub fn toJson(&self) -> Result<String, JsValue> {
        serde_json::to_string(self).map_err(|e| js_sys::Error::new(&e.to_string()).into())
    }
}

impl From<entropyk_fluids::ThermoState> for WasmThermoState {
    fn from(s: entropyk_fluids::ThermoState) -> Self {
        WasmThermoState {
            pressure: WasmPressure::from(s.pressure),
            temperature: WasmTemperature::from(s.temperature),
            enthalpy: WasmEnthalpy::from(s.enthalpy),
            mass_flow: WasmMassFlow { kg_per_s: 0.0 },
        }
    }
}

/// WASM wrapper for converged state (solver result).
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct WasmConvergedState {
    /// Whether the solver converged
    pub converged: bool,
    /// Number of iterations
    pub iterations: usize,
    /// Final residual norm
    pub final_residual: f64,
    pub(crate) status: String,
}

#[wasm_bindgen]
impl WasmConvergedState {
    /// Get solver status: "Converged", "TimedOut", "ControlSaturation", or "Unknown".
    pub fn status(&self) -> String {
        self.status.clone()
    }

    /// Convert to JSON string.
    pub fn toJson(&self) -> Result<String, JsValue> {
        serde_json::to_string(&SerializableConvergedState {
            converged: self.converged,
            iterations: self.iterations,
            final_residual: self.final_residual,
            status: &self.status,
        })
        .map_err(|e| js_sys::Error::new(&e.to_string()).into())
    }
}

/// Serializable representation of WasmConvergedState.
#[derive(Serialize)]
struct SerializableConvergedState<'a> {
    converged: bool,
    iterations: usize,
    final_residual: f64,
    status: &'a str,
}

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

    #[wasm_bindgen_test]
    fn test_pressure_creation() {
        let p = WasmPressure::from_bar(1.0).unwrap();
        assert!((p.pascals() - 100000.0).abs() < 1e-6);
        assert!((p.bar() - 1.0).abs() < 1e-9);
    }

    #[wasm_bindgen_test]
    fn test_pressure_rejects_negative() {
        assert!(WasmPressure::new(-1.0).is_err());
    }

    #[wasm_bindgen_test]
    fn test_pressure_rejects_nan() {
        assert!(WasmPressure::new(f64::NAN).is_err());
    }

    #[wasm_bindgen_test]
    fn test_temperature_creation() {
        let t = WasmTemperature::from_celsius(25.0).unwrap();
        assert!((t.kelvin() - 298.15).abs() < 1e-6);
        assert!((t.celsius() - 25.0).abs() < 1e-9);
    }

    #[wasm_bindgen_test]
    fn test_temperature_rejects_negative() {
        assert!(WasmTemperature::new(-1.0).is_err());
    }

    #[wasm_bindgen_test]
    fn test_enthalpy_creation() {
        let h = WasmEnthalpy::from_kj_per_kg(400.0).unwrap();
        assert!((h.joules_per_kg() - 400000.0).abs() < 1e-6);
        assert!((h.kj_per_kg() - 400.0).abs() < 1e-9);
    }

    #[wasm_bindgen_test]
    fn test_massflow_creation() {
        let m = WasmMassFlow::new(0.1).unwrap();
        assert!((m.kg_per_s() - 0.1).abs() < 1e-9);
    }
}
