//! Adiabatic capillary tube (1D segmented Bansal-style model).
//!
//! Integrates frictional ΔP along the tube with single-phase then two-phase
//! Müller-Steinhagen-Heck friction. Process is isenthalpic overall.

use crate::heat_exchanger::two_phase_dp::{msh_gradient, FriedelInput, TwoPhaseDpCorrelation};
use crate::port::{Connected, Disconnected, Port};
use crate::{
    CircuitId, Component, ComponentError, ConnectedPort, JacobianBuilder, OperationalState,
    ResidualVector, StateSlice,
};
use std::marker::PhantomData;

/// Capillary geometry and discretization.
#[derive(Debug, Clone, Copy)]
pub struct CapillaryGeometry {
    /// Inner diameter [m].
    pub diameter_m: f64,
    /// Tube length [m].
    pub length_m: f64,
    /// Number of axial segments (≥ 2).
    pub n_segments: usize,
}

impl Default for CapillaryGeometry {
    fn default() -> Self {
        Self {
            diameter_m: 0.001,
            length_m: 2.0,
            n_segments: 20,
        }
    }
}

/// Capillary tube component (2-port, isenthalpic).
#[derive(Debug, Clone)]
pub struct CapillaryTube<State> {
    geom: CapillaryGeometry,
    dp_correlation: TwoPhaseDpCorrelation,
    port_inlet: Port<State>,
    port_outlet: Port<State>,
    operational_state: OperationalState,
    circuit_id: CircuitId,
    /// Last computed ΔP [Pa].
    last_dp_pa: f64,
    _state: PhantomData<State>,
}

impl CapillaryTube<Disconnected> {
    /// Creates a disconnected capillary.
    pub fn new(
        geom: CapillaryGeometry,
        port_inlet: Port<Disconnected>,
        port_outlet: Port<Disconnected>,
    ) -> Result<Self, ComponentError> {
        if geom.diameter_m <= 0.0 || geom.length_m <= 0.0 || geom.n_segments < 2 {
            return Err(ComponentError::InvalidState(
                "invalid capillary geometry".into(),
            ));
        }
        Ok(Self {
            geom,
            dp_correlation: TwoPhaseDpCorrelation::MullerSteinhagenHeck1986,
            port_inlet,
            port_outlet,
            operational_state: OperationalState::On,
            circuit_id: CircuitId::default(),
            last_dp_pa: 0.0,
            _state: PhantomData,
        })
    }

    /// Connects ports.
    pub fn connect(
        self,
        inlet: Port<Disconnected>,
        outlet: Port<Disconnected>,
    ) -> Result<CapillaryTube<Connected>, ComponentError> {
        let (p_in, _) = self
            .port_inlet
            .connect(inlet)
            .map_err(|e| ComponentError::InvalidState(e.to_string()))?;
        let (p_out, _) = self
            .port_outlet
            .connect(outlet)
            .map_err(|e| ComponentError::InvalidState(e.to_string()))?;
        Ok(CapillaryTube {
            geom: self.geom,
            dp_correlation: self.dp_correlation,
            port_inlet: p_in,
            port_outlet: p_out,
            operational_state: self.operational_state,
            circuit_id: self.circuit_id,
            last_dp_pa: 0.0,
            _state: PhantomData,
        })
    }
}

impl CapillaryTube<Connected> {
    /// Integrates frictional pressure drop for given mass flow and quality path.
    ///
    /// Quality is linearly ramped from `x_in` to `x_out` along the tube (flashing).
    pub fn integrate_dp(
        &self,
        mass_flow_kg_s: f64,
        x_in: f64,
        x_out: f64,
        rho_l: f64,
        rho_v: f64,
        mu_l: f64,
        mu_v: f64,
        sigma: f64,
    ) -> f64 {
        let area = std::f64::consts::PI * (self.geom.diameter_m * 0.5).powi(2);
        let g = mass_flow_kg_s.abs() / area.max(1e-12);
        let dz = self.geom.length_m / self.geom.n_segments as f64;
        let mut dp = 0.0;
        for i in 0..self.geom.n_segments {
            let t = (i as f64 + 0.5) / self.geom.n_segments as f64;
            let x = x_in + (x_out - x_in) * t;
            let input = FriedelInput {
                mass_flux: g,
                diameter: self.geom.diameter_m,
                quality: x.clamp(0.0, 1.0),
                rho_liquid: rho_l,
                rho_vapor: rho_v,
                mu_liquid: mu_l,
                mu_vapor: mu_v,
                sigma,
            };
            let grad = match self.dp_correlation {
                TwoPhaseDpCorrelation::Friedel1979 => {
                    crate::heat_exchanger::two_phase_dp::friedel_gradient(&input)
                }
                TwoPhaseDpCorrelation::MullerSteinhagenHeck1986 => msh_gradient(&input),
            };
            dp += grad * dz;
        }
        dp
    }

    /// Last ΔP [Pa].
    pub fn last_dp_pa(&self) -> f64 {
        self.last_dp_pa
    }
}

impl Component for CapillaryTube<Connected> {
    fn compute_residuals(
        &self,
        state: &StateSlice,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        if state.len() < 4 {
            return Err(ComponentError::InvalidStateDimensions {
                expected: 4,
                actual: state.len(),
            });
        }
        if residuals.len() < 2 {
            return Err(ComponentError::InvalidResidualDimensions {
                expected: 2,
                actual: residuals.len(),
            });
        }
        // state: [m, P_in, h_in, P_out] (simplified) or [m_in, m_out, h_in, h_out]
        let m = state[0];
        residuals[0] = state[1] - m; // mass continuity if state[1]=m_out
                                     // isenthalpic: h_out - h_in = 0
        if state.len() >= 4 {
            residuals[1] = state[3] - state[2];
        } else {
            residuals[1] = 0.0;
        }
        Ok(())
    }

    fn jacobian_entries(
        &self,
        _state: &StateSlice,
        jacobian: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        jacobian.add_entry(0, 0, -1.0);
        jacobian.add_entry(0, 1, 1.0);
        jacobian.add_entry(1, 2, -1.0);
        jacobian.add_entry(1, 3, 1.0);
        Ok(())
    }

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

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

    fn signature(&self) -> String {
        format!(
            "CapillaryTube(D={:.2}mm, L={:.2}m, N={})",
            self.geom.diameter_m * 1000.0,
            self.geom.length_m,
            self.geom.n_segments
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::port::FluidId;
    use entropyk_core::{Enthalpy, Pressure};

    #[test]
    fn dp_increases_with_mass_flow() {
        let geom = CapillaryGeometry::default();
        let inlet = Port::new(
            FluidId::new("R134a"),
            Pressure::from_bar(10.0),
            Enthalpy::from_joules_per_kg(250_000.0),
        );
        let outlet = Port::new(
            FluidId::new("R134a"),
            Pressure::from_bar(3.0),
            Enthalpy::from_joules_per_kg(250_000.0),
        );
        let cap = CapillaryTube::new(geom, inlet, outlet)
            .unwrap()
            .connect(
                Port::new(
                    FluidId::new("R134a"),
                    Pressure::from_bar(10.0),
                    Enthalpy::from_joules_per_kg(250_000.0),
                ),
                Port::new(
                    FluidId::new("R134a"),
                    Pressure::from_bar(3.0),
                    Enthalpy::from_joules_per_kg(250_000.0),
                ),
            )
            .unwrap();
        let dp_low = cap.integrate_dp(0.005, 0.0, 0.3, 1200.0, 20.0, 2e-4, 1.2e-5, 0.01);
        let dp_high = cap.integrate_dp(0.015, 0.0, 0.3, 1200.0, 20.0, 2e-4, 1.2e-5, 0.01);
        assert!(dp_high > dp_low);
        assert!(dp_low > 0.0);
    }
}
