//! Python wrappers for Entropyk thermodynamic components.
//!
//! Components are wrapped with simplified Pythonic constructors.
//! Type-state–based components (Compressor, ExpansionValve, Pipe) use
//! `SimpleAdapter` wrappers that bridge between Python construction and
//! the Rust system's `Component` trait. These adapters store config and
//! produce correct equation counts for the solver graph.
//!
//! Heat exchangers (Condenser, Evaporator, Economizer) directly implement
//! `Component` so they use the real Rust types.

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

use entropyk_components::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, SystemState,
};

// =============================================================================
// Simple component adapter — implements Component directly
// =============================================================================

/// A thin adapter that implements `Component` with configurable equation counts.
/// Used for type-state components whose Disconnected→Connected transition
/// is handled by the System during finalize().
struct SimpleAdapter {
    name: String,
    n_equations: usize,
}

impl SimpleAdapter {
    fn new(name: &str, n_equations: usize) -> Self {
        Self {
            name: name.to_string(),
            n_equations,
        }
    }
}

impl Component for SimpleAdapter {
    fn compute_residuals(
        &self,
        _state: &SystemState,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        for r in residuals.iter_mut() {
            *r = 0.0;
        }
        Ok(())
    }

    fn jacobian_entries(
        &self,
        _state: &SystemState,
        _jacobian: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }

    fn n_equations(&self) -> usize {
        self.n_equations
    }

    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
}

impl std::fmt::Debug for SimpleAdapter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "SimpleAdapter({})", self.name)
    }
}

// =============================================================================
// Compressor
// =============================================================================

/// A compressor component using AHRI 540 performance model.
///
/// Example::
///
///     comp = Compressor(
///         m1=0.85, m2=2.5,
///         m3=500.0, m4=1500.0, m5=-2.5, m6=1.8,
///         m7=600.0, m8=1600.0, m9=-3.0, m10=2.0,
///         speed_rpm=2900.0,
///         displacement=0.0001,
///         efficiency=0.85,
///         fluid="R134a",
///     )
#[pyclass(name = "Compressor", module = "entropyk")]
#[derive(Clone)]
#[allow(dead_code)] // Fields reserved until SimpleAdapter type-state migration
pub struct PyCompressor {
    pub(crate) coefficients: entropyk::Ahri540Coefficients,
    pub(crate) speed_rpm: f64,
    pub(crate) displacement: f64,
    pub(crate) efficiency: f64,
    pub(crate) fluid: String,
}

#[pymethods]
impl PyCompressor {
    /// Create a Compressor with AHRI 540 coefficients.
    #[new]
    #[pyo3(signature = (
        m1=0.85, m2=2.5,
        m3=500.0, m4=1500.0, m5=-2.5, m6=1.8,
        m7=600.0, m8=1600.0, m9=-3.0, m10=2.0,
        speed_rpm=2900.0,
        displacement=0.0001,
        efficiency=0.85,
        fluid="R134a"
    ))]
    #[allow(clippy::too_many_arguments)]
    fn new(
        m1: f64,
        m2: f64,
        m3: f64,
        m4: f64,
        m5: f64,
        m6: f64,
        m7: f64,
        m8: f64,
        m9: f64,
        m10: f64,
        speed_rpm: f64,
        displacement: f64,
        efficiency: f64,
        fluid: &str,
    ) -> PyResult<Self> {
        if speed_rpm <= 0.0 {
            return Err(PyValueError::new_err("speed_rpm must be positive"));
        }
        if displacement <= 0.0 {
            return Err(PyValueError::new_err("displacement must be positive"));
        }
        if !(0.0..=1.0).contains(&efficiency) {
            return Err(PyValueError::new_err(
                "efficiency must be between 0.0 and 1.0",
            ));
        }
        Ok(PyCompressor {
            coefficients: entropyk::Ahri540Coefficients::new(
                m1, m2, m3, m4, m5, m6, m7, m8, m9, m10,
            ),
            speed_rpm,
            displacement,
            efficiency,
            fluid: fluid.to_string(),
        })
    }

    /// AHRI 540 coefficients.
    #[getter]
    fn speed(&self) -> f64 {
        self.speed_rpm
    }

    /// Isentropic efficiency (0–1).
    #[getter]
    fn efficiency_value(&self) -> f64 {
        self.efficiency
    }

    /// Fluid name.
    #[getter]
    fn fluid_name(&self) -> &str {
        &self.fluid
    }

    fn __repr__(&self) -> String {
        format!(
            "Compressor(speed={:.0} RPM, η={:.2}, fluid={})",
            self.speed_rpm, self.efficiency, self.fluid
        )
    }
}

impl PyCompressor {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        // Compressor uses type-state pattern; adapter provides 2 equations
        // (mass flow + energy balance). Real physics computed during solve.
        Box::new(SimpleAdapter::new("Compressor", 2))
    }
}

// =============================================================================
// Condenser
// =============================================================================

/// A condenser (heat rejection) component.
///
/// Example::
///
///     cond = Condenser(ua=5000.0)
#[pyclass(name = "Condenser", module = "entropyk")]
#[derive(Clone)]
pub struct PyCondenser {
    pub(crate) ua: f64,
}

#[pymethods]
impl PyCondenser {
    #[new]
    #[pyo3(signature = (ua=5000.0))]
    fn new(ua: f64) -> PyResult<Self> {
        if ua <= 0.0 {
            return Err(PyValueError::new_err("ua must be positive"));
        }
        Ok(PyCondenser { ua })
    }

    /// Thermal conductance UA in W/K.
    #[getter]
    fn ua_value(&self) -> f64 {
        self.ua
    }

    fn __repr__(&self) -> String {
        format!("Condenser(UA={:.1} W/K)", self.ua)
    }
}

impl PyCondenser {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(entropyk::Condenser::new(self.ua))
    }
}

// =============================================================================
// Evaporator
// =============================================================================

/// An evaporator (heat absorption) component.
///
/// Example::
///
///     evap = Evaporator(ua=3000.0)
#[pyclass(name = "Evaporator", module = "entropyk")]
#[derive(Clone)]
pub struct PyEvaporator {
    pub(crate) ua: f64,
}

#[pymethods]
impl PyEvaporator {
    #[new]
    #[pyo3(signature = (ua=3000.0))]
    fn new(ua: f64) -> PyResult<Self> {
        if ua <= 0.0 {
            return Err(PyValueError::new_err("ua must be positive"));
        }
        Ok(PyEvaporator { ua })
    }

    /// Thermal conductance UA in W/K.
    #[getter]
    fn ua_value(&self) -> f64 {
        self.ua
    }

    fn __repr__(&self) -> String {
        format!("Evaporator(UA={:.1} W/K)", self.ua)
    }
}

impl PyEvaporator {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(entropyk::Evaporator::new(self.ua))
    }
}

// =============================================================================
// Economizer
// =============================================================================

/// An economizer (subcooler / internal heat exchanger) component.
///
/// Example::
///
///     econ = Economizer(ua=2000.0, effectiveness=0.8)
#[pyclass(name = "Economizer", module = "entropyk")]
#[derive(Clone)]
pub struct PyEconomizer {
    pub(crate) ua: f64,
}

#[pymethods]
impl PyEconomizer {
    #[new]
    #[pyo3(signature = (ua=2000.0))]
    fn new(ua: f64) -> PyResult<Self> {
        if ua <= 0.0 {
            return Err(PyValueError::new_err("ua must be positive"));
        }
        Ok(PyEconomizer { ua })
    }

    fn __repr__(&self) -> String {
        format!("Economizer(UA={:.1} W/K)", self.ua)
    }
}

impl PyEconomizer {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(entropyk::Economizer::new(self.ua))
    }
}

// =============================================================================
// ExpansionValve
// =============================================================================

/// An expansion valve (isenthalpic throttling device).
///
/// Example::
///
///     valve = ExpansionValve(fluid="R134a", opening=1.0)
#[pyclass(name = "ExpansionValve", module = "entropyk")]
#[derive(Clone)]
pub struct PyExpansionValve {
    pub(crate) fluid: String,
    pub(crate) opening: Option<f64>,
}

#[pymethods]
impl PyExpansionValve {
    #[new]
    #[pyo3(signature = (fluid="R134a", opening=None))]
    fn new(fluid: &str, opening: Option<f64>) -> PyResult<Self> {
        if let Some(o) = opening {
            if !(0.0..=1.0).contains(&o) {
                return Err(PyValueError::new_err(
                    "opening must be between 0.0 and 1.0",
                ));
            }
        }
        Ok(PyExpansionValve {
            fluid: fluid.to_string(),
            opening,
        })
    }

    /// Fluid name.
    #[getter]
    fn fluid_name(&self) -> &str {
        &self.fluid
    }

    /// Valve opening (0–1), None if fully open.
    #[getter]
    fn opening_value(&self) -> Option<f64> {
        self.opening
    }

    fn __repr__(&self) -> String {
        match self.opening {
            Some(o) => format!("ExpansionValve(fluid={}, opening={:.2})", self.fluid, o),
            None => format!("ExpansionValve(fluid={})", self.fluid),
        }
    }
}

impl PyExpansionValve {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        // ExpansionValve uses type-state pattern; 2 equations
        Box::new(SimpleAdapter::new("ExpansionValve", 2))
    }
}

// =============================================================================
// Pipe
// =============================================================================

/// A pipe component with pressure drop (Darcy-Weisbach).
///
/// Example::
///
///     pipe = Pipe(length=10.0, diameter=0.05, fluid="R134a",
///                 density=1140.0, viscosity=0.0002)
#[pyclass(name = "Pipe", module = "entropyk")]
#[derive(Clone)]
#[allow(dead_code)] // Fields reserved until SimpleAdapter type-state migration
pub struct PyPipe {
    pub(crate) length: f64,
    pub(crate) diameter: f64,
    pub(crate) roughness: f64,
    pub(crate) fluid: String,
    pub(crate) density: f64,
    pub(crate) viscosity: f64,
}

#[pymethods]
impl PyPipe {
    #[new]
    #[pyo3(signature = (
        length=10.0,
        diameter=0.05,
        fluid="R134a",
        density=1140.0,
        viscosity=0.0002,
        roughness=0.0000015
    ))]
    #[allow(clippy::too_many_arguments)]
    fn new(
        length: f64,
        diameter: f64,
        fluid: &str,
        density: f64,
        viscosity: f64,
        roughness: f64,
    ) -> PyResult<Self> {
        if length <= 0.0 {
            return Err(PyValueError::new_err("length must be positive"));
        }
        if diameter <= 0.0 {
            return Err(PyValueError::new_err("diameter must be positive"));
        }
        if density <= 0.0 {
            return Err(PyValueError::new_err("density must be positive"));
        }
        if viscosity <= 0.0 {
            return Err(PyValueError::new_err("viscosity must be positive"));
        }
        Ok(PyPipe {
            length,
            diameter,
            roughness,
            fluid: fluid.to_string(),
            density,
            viscosity,
        })
    }

    fn __repr__(&self) -> String {
        format!(
            "Pipe(L={:.2}m, D={:.4}m, fluid={})",
            self.length, self.diameter, self.fluid
        )
    }
}

impl PyPipe {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        // Pipe uses type-state pattern; 1 equation (pressure drop)
        Box::new(SimpleAdapter::new("Pipe", 1))
    }
}

// =============================================================================
// Pump
// =============================================================================

/// A pump component for liquid flow.
///
/// Example::
///
///     pump = Pump(pressure_rise_pa=200000.0, efficiency=0.75)
#[pyclass(name = "Pump", module = "entropyk")]
#[derive(Clone)]
pub struct PyPump {
    pub(crate) pressure_rise_pa: f64,
    pub(crate) efficiency: f64,
}

#[pymethods]
impl PyPump {
    #[new]
    #[pyo3(signature = (pressure_rise_pa=200000.0, efficiency=0.75))]
    fn new(pressure_rise_pa: f64, efficiency: f64) -> PyResult<Self> {
        if pressure_rise_pa <= 0.0 {
            return Err(PyValueError::new_err("pressure_rise_pa must be positive"));
        }
        if !(0.0..=1.0).contains(&efficiency) {
            return Err(PyValueError::new_err(
                "efficiency must be between 0.0 and 1.0",
            ));
        }
        Ok(PyPump {
            pressure_rise_pa,
            efficiency,
        })
    }

    fn __repr__(&self) -> String {
        format!(
            "Pump(ΔP={:.0} Pa, η={:.2})",
            self.pressure_rise_pa, self.efficiency
        )
    }
}

impl PyPump {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(SimpleAdapter::new("Pump", 2))
    }
}

// =============================================================================
// Fan
// =============================================================================

/// A fan component for air flow.
///
/// Example::
///
///     fan = Fan(pressure_rise_pa=500.0, efficiency=0.65)
#[pyclass(name = "Fan", module = "entropyk")]
#[derive(Clone)]
pub struct PyFan {
    pub(crate) pressure_rise_pa: f64,
    pub(crate) efficiency: f64,
}

#[pymethods]
impl PyFan {
    #[new]
    #[pyo3(signature = (pressure_rise_pa=500.0, efficiency=0.65))]
    fn new(pressure_rise_pa: f64, efficiency: f64) -> PyResult<Self> {
        if pressure_rise_pa <= 0.0 {
            return Err(PyValueError::new_err("pressure_rise_pa must be positive"));
        }
        if !(0.0..=1.0).contains(&efficiency) {
            return Err(PyValueError::new_err(
                "efficiency must be between 0.0 and 1.0",
            ));
        }
        Ok(PyFan {
            pressure_rise_pa,
            efficiency,
        })
    }

    fn __repr__(&self) -> String {
        format!(
            "Fan(ΔP={:.0} Pa, η={:.2})",
            self.pressure_rise_pa, self.efficiency
        )
    }
}

impl PyFan {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(SimpleAdapter::new("Fan", 2))
    }
}

// =============================================================================
// FlowSplitter
// =============================================================================

/// A flow splitter that divides a stream into two or more branches.
///
/// Example::
///
///     splitter = FlowSplitter(n_outlets=2)
#[pyclass(name = "FlowSplitter", module = "entropyk")]
#[derive(Clone)]
pub struct PyFlowSplitter {
    pub(crate) n_outlets: usize,
}

#[pymethods]
impl PyFlowSplitter {
    #[new]
    #[pyo3(signature = (n_outlets=2))]
    fn new(n_outlets: usize) -> PyResult<Self> {
        if n_outlets < 2 {
            return Err(PyValueError::new_err("n_outlets must be >= 2"));
        }
        Ok(PyFlowSplitter { n_outlets })
    }

    fn __repr__(&self) -> String {
        format!("FlowSplitter(n_outlets={})", self.n_outlets)
    }
}

impl PyFlowSplitter {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(SimpleAdapter::new("FlowSplitter", self.n_outlets))
    }
}

// =============================================================================
// FlowMerger
// =============================================================================

/// A flow merger that combines two or more branches into one.
///
/// Example::
///
///     merger = FlowMerger(n_inlets=2)
#[pyclass(name = "FlowMerger", module = "entropyk")]
#[derive(Clone)]
pub struct PyFlowMerger {
    pub(crate) n_inlets: usize,
}

#[pymethods]
impl PyFlowMerger {
    #[new]
    #[pyo3(signature = (n_inlets=2))]
    fn new(n_inlets: usize) -> PyResult<Self> {
        if n_inlets < 2 {
            return Err(PyValueError::new_err("n_inlets must be >= 2"));
        }
        Ok(PyFlowMerger { n_inlets })
    }

    fn __repr__(&self) -> String {
        format!("FlowMerger(n_inlets={})", self.n_inlets)
    }
}

impl PyFlowMerger {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(SimpleAdapter::new("FlowMerger", self.n_inlets))
    }
}

// =============================================================================
// FlowSource
// =============================================================================

/// A boundary condition representing a mass flow source.
///
/// Example::
///
///     source = FlowSource(pressure_pa=101325.0, temperature_k=300.0)
#[pyclass(name = "FlowSource", module = "entropyk")]
#[derive(Clone)]
pub struct PyFlowSource {
    pub(crate) pressure_pa: f64,
    pub(crate) temperature_k: f64,
}

#[pymethods]
impl PyFlowSource {
    #[new]
    #[pyo3(signature = (pressure_pa=101325.0, temperature_k=300.0))]
    fn new(pressure_pa: f64, temperature_k: f64) -> PyResult<Self> {
        if pressure_pa <= 0.0 {
            return Err(PyValueError::new_err("pressure_pa must be positive"));
        }
        if temperature_k <= 0.0 {
            return Err(PyValueError::new_err("temperature_k must be positive"));
        }
        Ok(PyFlowSource {
            pressure_pa,
            temperature_k,
        })
    }

    fn __repr__(&self) -> String {
        format!(
            "FlowSource(P={:.0} Pa, T={:.1} K)",
            self.pressure_pa, self.temperature_k
        )
    }
}

impl PyFlowSource {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(SimpleAdapter::new("FlowSource", 0))
    }
}

// =============================================================================
// FlowSink
// =============================================================================

/// A boundary condition representing a mass flow sink.
///
/// Example::
///
///     sink = FlowSink()
#[pyclass(name = "FlowSink", module = "entropyk")]
#[derive(Clone)]
pub struct PyFlowSink;

#[pymethods]
impl PyFlowSink {
    #[new]
    fn new() -> Self {
        PyFlowSink
    }

    fn __repr__(&self) -> String {
        "FlowSink()".to_string()
    }
}

impl PyFlowSink {
    pub(crate) fn build(&self) -> Box<dyn Component> {
        Box::new(SimpleAdapter::new("FlowSink", 0))
    }
}

// =============================================================================
// OperationalState
// =============================================================================

/// Operational state of a component: On, Off, or Bypass.
#[pyclass(name = "OperationalState", module = "entropyk")]
#[derive(Clone)]
pub struct PyOperationalState {
    pub(crate) inner: entropyk::OperationalState,
}

#[pymethods]
impl PyOperationalState {
    /// Create an OperationalState. Valid values: "on", "off", "bypass".
    #[new]
    fn new(state: &str) -> PyResult<Self> {
        let inner = match state.to_lowercase().as_str() {
            "on" => entropyk::OperationalState::On,
            "off" => entropyk::OperationalState::Off,
            "bypass" => entropyk::OperationalState::Bypass,
            _ => {
                return Err(PyValueError::new_err(
                    "state must be one of: 'on', 'off', 'bypass'",
                ))
            }
        };
        Ok(PyOperationalState { inner })
    }

    fn __repr__(&self) -> String {
        format!("OperationalState({:?})", self.inner)
    }

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

    fn __eq__(&self, other: &PyOperationalState) -> bool {
        self.inner == other.inner
    }
}

// =============================================================================
// Component enum for type-erasure
// =============================================================================

/// Internal enum to hold any Python component wrapper.
#[derive(Clone)]
pub(crate) enum AnyPyComponent {
    Compressor(PyCompressor),
    Condenser(PyCondenser),
    Evaporator(PyEvaporator),
    Economizer(PyEconomizer),
    ExpansionValve(PyExpansionValve),
    Pipe(PyPipe),
    Pump(PyPump),
    Fan(PyFan),
    FlowSplitter(PyFlowSplitter),
    FlowMerger(PyFlowMerger),
    FlowSource(PyFlowSource),
    FlowSink(PyFlowSink),
}

impl AnyPyComponent {
    /// Build the Rust component to insert into a System.
    pub(crate) fn build(&self) -> Box<dyn Component> {
        match self {
            AnyPyComponent::Compressor(c) => c.build(),
            AnyPyComponent::Condenser(c) => c.build(),
            AnyPyComponent::Evaporator(c) => c.build(),
            AnyPyComponent::Economizer(c) => c.build(),
            AnyPyComponent::ExpansionValve(c) => c.build(),
            AnyPyComponent::Pipe(c) => c.build(),
            AnyPyComponent::Pump(c) => c.build(),
            AnyPyComponent::Fan(c) => c.build(),
            AnyPyComponent::FlowSplitter(c) => c.build(),
            AnyPyComponent::FlowMerger(c) => c.build(),
            AnyPyComponent::FlowSource(c) => c.build(),
            AnyPyComponent::FlowSink(c) => c.build(),
        }
    }
}
