//! WASM component bindings.
//!
//! Provides JavaScript-friendly wrappers for thermodynamic components.

use entropyk_components::port::{FluidId, Port};
use entropyk_components::Component;
use wasm_bindgen::prelude::*;

/// WASM wrapper for a thermodynamic component.
#[wasm_bindgen]
pub struct WasmComponent {
    pub(crate) inner: Box<dyn Component>,
    name: String,
}

#[wasm_bindgen]
impl WasmComponent {
    /// Get component type name.
    pub fn name(&self) -> String {
        self.name.clone()
    }
}

/// WASM wrapper for Compressor.
#[wasm_bindgen]
pub struct WasmCompressor {
    pub(crate) inner: entropyk_components::Compressor<entropyk_components::port::Connected>,
}

#[wasm_bindgen]
impl WasmCompressor {
    /// Create a new Compressor with AHRI 540 performance model.
    ///
    /// # Arguments
    /// * `fluid` - Fluid identifier (e.g. "R134a", "R410A")
    /// * `speed_rpm` - Rotational speed in RPM
    /// * `displacement_m3_per_rev` - Displacement volume in m³/rev
    /// * `efficiency` - Mechanical efficiency (0.0 to 1.0)
    /// * `m1`..`m10` - AHRI 540 performance coefficients
    #[wasm_bindgen(constructor)]
    pub fn new(
        fluid: String,
        speed_rpm: f64,
        displacement_m3_per_rev: f64,
        efficiency: f64,
        m1: f64,
        m2: f64,
        m3: f64,
        m4: f64,
        m5: f64,
        m6: f64,
        m7: f64,
        m8: f64,
        m9: f64,
        m10: f64,
    ) -> Result<WasmCompressor, JsValue> {
        if speed_rpm <= 0.0 {
            return Err(js_sys::Error::new("speed_rpm must be positive").into());
        }
        if displacement_m3_per_rev <= 0.0 {
            return Err(js_sys::Error::new("displacement_m3_per_rev must be positive").into());
        }
        if !(0.0..=1.0).contains(&efficiency) {
            return Err(js_sys::Error::new("efficiency must be between 0.0 and 1.0").into());
        }

        let coeffs =
            entropyk_components::Ahri540Coefficients::new(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10);
        let fluid_id = FluidId::new(&fluid);
        let p = entropyk_core::Pressure::from_bar(5.0);
        let h = entropyk_core::Enthalpy::from_joules_per_kg(400000.0);
        let suction = Port::new(fluid_id.clone(), p, h);
        let discharge = Port::new(fluid_id.clone(), p, h);

        let comp = entropyk_components::Compressor::new(
            coeffs,
            suction,
            discharge,
            speed_rpm,
            displacement_m3_per_rev,
            efficiency,
        )
        .map_err(|e| js_sys::Error::new(&format!("Compressor creation failed: {}", e)))?;

        let suction_p = Port::new(fluid_id.clone(), p, h);
        let discharge_p = Port::new(fluid_id, p, h);
        let connected = comp
            .connect(suction_p, discharge_p)
            .map_err(|e| js_sys::Error::new(&format!("Compressor connect failed: {}", e)))?;

        Ok(WasmCompressor { inner: connected })
    }

    /// Convert to a generic WasmComponent.
    pub fn into_component(self) -> WasmComponent {
        WasmComponent {
            inner: Box::new(self.inner),
            name: "Compressor".to_string(),
        }
    }
}

/// WASM wrapper for a screw compressor with economizer injection.
#[wasm_bindgen]
pub struct WasmScrewEconomizerCompressor {
    pub(crate) inner: entropyk_components::ScrewEconomizerCompressor,
}

#[wasm_bindgen]
impl WasmScrewEconomizerCompressor {
    /// Create a new screw economizer compressor with default bilinear curves.
    #[wasm_bindgen(constructor)]
    pub fn new(
        fluid: String,
        nominal_frequency_hz: f64,
        frequency_hz: f64,
        mechanical_efficiency: f64,
        economizer_fraction: f64,
    ) -> Result<WasmScrewEconomizerCompressor, JsValue> {
        if nominal_frequency_hz <= 0.0 {
            return Err(js_sys::Error::new("nominal_frequency_hz must be positive").into());
        }
        if frequency_hz < 0.0 {
            return Err(js_sys::Error::new("frequency_hz cannot be negative").into());
        }
        if !(0.0..=1.0).contains(&mechanical_efficiency) || mechanical_efficiency == 0.0 {
            return Err(js_sys::Error::new("mechanical_efficiency must be in (0.0, 1.0]").into());
        }
        if !(0.0..=0.4).contains(&economizer_fraction) {
            return Err(
                js_sys::Error::new("economizer_fraction must be between 0.0 and 0.4").into(),
            );
        }

        fn connected_port(
            fluid: &str,
            pressure_bar: f64,
            enthalpy_kj_kg: f64,
        ) -> Result<entropyk_components::ConnectedPort, JsValue> {
            let fluid_id = FluidId::new(fluid);
            let port_a = Port::new(
                fluid_id.clone(),
                entropyk_core::Pressure::from_bar(pressure_bar),
                entropyk_core::Enthalpy::from_joules_per_kg(enthalpy_kj_kg * 1000.0),
            );
            let port_b = Port::new(
                fluid_id,
                entropyk_core::Pressure::from_bar(pressure_bar),
                entropyk_core::Enthalpy::from_joules_per_kg(enthalpy_kj_kg * 1000.0),
            );
            port_a
                .connect(port_b)
                .map(|(connected, _)| connected)
                .map_err(|e| js_sys::Error::new(&format!("Port connection failed: {}", e)).into())
        }

        let curves = entropyk_components::ScrewPerformanceCurves::with_fixed_eco_fraction(
            entropyk_components::Polynomial2D::bilinear(1.2, 0.003, -0.002, 0.000_01),
            entropyk_components::Polynomial2D::bilinear(55_000.0, 200.0, -300.0, 0.5),
            economizer_fraction,
        );
        let mut inner = entropyk_components::ScrewEconomizerCompressor::new(
            curves,
            &fluid,
            nominal_frequency_hz,
            mechanical_efficiency,
            connected_port(&fluid, 3.2, 400.0)?,
            connected_port(&fluid, 12.8, 440.0)?,
            connected_port(&fluid, 6.4, 260.0)?,
        )
        .map_err(|e| js_sys::Error::new(&format!("Screw compressor creation failed: {}", e)))?;
        inner
            .set_frequency_hz(frequency_hz)
            .map_err(|e| js_sys::Error::new(&format!("Frequency update failed: {}", e)))?;

        Ok(WasmScrewEconomizerCompressor { inner })
    }

    /// Current VFD frequency in Hz.
    pub fn frequency_hz(&self) -> f64 {
        self.inner.frequency_hz()
    }

    /// Convert to a generic WasmComponent.
    pub fn into_component(self) -> WasmComponent {
        WasmComponent {
            inner: Box::new(self.inner),
            name: "ScrewEconomizerCompressor".to_string(),
        }
    }
}

/// WASM wrapper for Condenser.
#[wasm_bindgen]
pub struct WasmCondenser {
    inner: entropyk_components::Condenser,
}

#[wasm_bindgen]
impl WasmCondenser {
    /// Create a new condenser with thermal conductance UA (W/K).
    ///
    /// Rejects NaN, negative, zero, and infinite values.
    #[wasm_bindgen(constructor)]
    pub fn new(ua: f64) -> Result<WasmCondenser, JsValue> {
        if ua.is_nan() || ua.is_infinite() {
            return Err(js_sys::Error::new("UA must be a finite number").into());
        }
        if ua <= 0.0 {
            return Err(js_sys::Error::new("UA must be positive").into());
        }
        Ok(WasmCondenser {
            inner: entropyk_components::Condenser::new(ua),
        })
    }

    /// Convert to a generic WasmComponent.
    pub fn into_component(self) -> WasmComponent {
        WasmComponent {
            inner: Box::new(self.inner),
            name: "Condenser".to_string(),
        }
    }
}

/// WASM wrapper for Evaporator.
#[wasm_bindgen]
pub struct WasmEvaporator {
    inner: entropyk_components::Evaporator,
}

#[wasm_bindgen]
impl WasmEvaporator {
    /// Create a new evaporator with thermal conductance UA (W/K).
    ///
    /// Rejects NaN, negative, zero, and infinite values.
    #[wasm_bindgen(constructor)]
    pub fn new(ua: f64) -> Result<WasmEvaporator, JsValue> {
        if ua.is_nan() || ua.is_infinite() {
            return Err(js_sys::Error::new("UA must be a finite number").into());
        }
        if ua <= 0.0 {
            return Err(js_sys::Error::new("UA must be positive").into());
        }
        Ok(WasmEvaporator {
            inner: entropyk_components::Evaporator::new(ua),
        })
    }

    /// Convert to a generic WasmComponent.
    pub fn into_component(self) -> WasmComponent {
        WasmComponent {
            inner: Box::new(self.inner),
            name: "Evaporator".to_string(),
        }
    }
}

/// WASM wrapper for ExpansionValve.
#[wasm_bindgen]
pub struct WasmExpansionValve {
    pub(crate) inner: entropyk_components::ExpansionValve<entropyk_components::port::Connected>,
}

#[wasm_bindgen]
impl WasmExpansionValve {
    /// Create a new expansion valve.
    ///
    /// # Arguments
    /// * `fluid` - Fluid identifier (e.g. "R134a", "R410A")
    /// * `capacity` - Optional valve capacity (kW). Pass 0.0 for no capacity limit.
    #[wasm_bindgen(constructor)]
    pub fn new(fluid: String, capacity: f64) -> Result<WasmExpansionValve, JsValue> {
        let fluid_id = FluidId::new(&fluid);
        let p = entropyk_core::Pressure::from_bar(10.0);
        let h = entropyk_core::Enthalpy::from_joules_per_kg(400000.0);
        let inlet = Port::new(fluid_id.clone(), p, h);
        let outlet = Port::new(fluid_id.clone(), p, h);

        let cap = if capacity > 0.0 { Some(capacity) } else { None };
        let valve = entropyk_components::ExpansionValve::new(inlet, outlet, cap)
            .map_err(|e| js_sys::Error::new(&format!("ExpansionValve creation failed: {}", e)))?;

        let inlet_p = Port::new(fluid_id.clone(), p, h);
        let outlet_p = Port::new(fluid_id, p, h);
        let connected = valve
            .connect(inlet_p, outlet_p)
            .map_err(|e| js_sys::Error::new(&format!("ExpansionValve connect failed: {}", e)))?;

        Ok(WasmExpansionValve { inner: connected })
    }

    /// Convert to a generic WasmComponent.
    pub fn into_component(self) -> WasmComponent {
        WasmComponent {
            inner: Box::new(self.inner),
            name: "ExpansionValve".to_string(),
        }
    }
}

/// WASM wrapper for Pipe.
#[wasm_bindgen]
pub struct WasmPipe {
    pub(crate) inner: entropyk_components::Pipe<entropyk_components::port::Connected>,
}

#[wasm_bindgen]
impl WasmPipe {
    /// Create a new pipe.
    ///
    /// # Arguments
    /// * `fluid` - Fluid identifier (e.g. "Water")
    /// * `length` - Pipe length in meters (must be positive)
    /// * `diameter` - Pipe inner diameter in meters (must be positive)
    /// * `density` - Fluid density in kg/m³ (default: 1000.0 for water)
    /// * `viscosity` - Fluid dynamic viscosity in Pa·s (default: 0.001 for water)
    #[wasm_bindgen(constructor)]
    pub fn new(
        fluid: String,
        length: f64,
        diameter: f64,
        density: f64,
        viscosity: f64,
    ) -> Result<WasmPipe, JsValue> {
        if length.is_nan() || length <= 0.0 {
            return Err(js_sys::Error::new("Pipe length must be a positive number").into());
        }
        if diameter.is_nan() || diameter <= 0.0 {
            return Err(js_sys::Error::new("Pipe diameter must be a positive number").into());
        }

        let geometry = entropyk_components::PipeGeometry::smooth(length, diameter)
            .map_err(|e| js_sys::Error::new(&format!("Invalid pipe geometry: {}", e)))?;

        let fluid_id = FluidId::new(&fluid);
        let p = entropyk_core::Pressure::from_bar(1.0);
        let h = entropyk_core::Enthalpy::from_joules_per_kg(100000.0);
        let inlet = Port::new(fluid_id.clone(), p, h);
        let outlet = Port::new(fluid_id.clone(), p, h);

        let pipe = entropyk_components::Pipe::new(geometry, inlet, outlet, density, viscosity)
            .map_err(|e| js_sys::Error::new(&format!("Pipe creation failed: {}", e)))?;

        let inlet_p = Port::new(fluid_id.clone(), p, h);
        let outlet_p = Port::new(fluid_id, p, h);
        let connected = pipe
            .connect(inlet_p, outlet_p)
            .map_err(|e| js_sys::Error::new(&format!("Pipe connect failed: {}", e)))?;

        Ok(WasmPipe { inner: connected })
    }

    /// Convert to a generic WasmComponent.
    pub fn into_component(self) -> WasmComponent {
        WasmComponent {
            inner: Box::new(self.inner),
            name: "Pipe".to_string(),
        }
    }
}

/// WASM wrapper for CapillaryTube.
#[wasm_bindgen]
pub struct WasmCapillaryTube {
    pub(crate) inner: entropyk_components::CapillaryTube<entropyk_components::port::Connected>,
}

#[wasm_bindgen]
impl WasmCapillaryTube {
    /// Create a capillary tube (diameter_m, length_m).
    #[wasm_bindgen(constructor)]
    pub fn new(
        fluid: String,
        diameter_m: f64,
        length_m: f64,
    ) -> Result<WasmCapillaryTube, JsValue> {
        use entropyk_components::{CapillaryGeometry, CapillaryTube};
        let fluid_id = FluidId::new(&fluid);
        let p = entropyk_core::Pressure::from_bar(10.0);
        let h = entropyk_core::Enthalpy::from_joules_per_kg(250000.0);
        let geom = CapillaryGeometry {
            diameter_m,
            length_m,
            n_segments: 20,
        };
        let tube = CapillaryTube::new(
            geom,
            Port::new(fluid_id.clone(), p, h),
            Port::new(fluid_id.clone(), p, h),
        )
        .map_err(|e| js_sys::Error::new(&format!("{e}")))?;
        let connected = tube
            .connect(Port::new(fluid_id.clone(), p, h), Port::new(fluid_id, p, h))
            .map_err(|e| js_sys::Error::new(&format!("{e}")))?;
        Ok(WasmCapillaryTube { inner: connected })
    }

    /// Convert to a generic WasmComponent.
    pub fn into_component(self) -> WasmComponent {
        WasmComponent {
            inner: Box::new(self.inner),
            name: "CapillaryTube".to_string(),
        }
    }
}
