//! # Entropyk Components
//!
//! This crate provides the core component trait definitions for the Entropyk
//! thermodynamic simulation library. All thermodynamic components (compressors,
//! condensers, evaporators, etc.) implement the [`Component`] trait.
//!
//! ## Core Concept
//!
//! The [`Component`] trait defines the interface between thermodynamic components
//! and the solver engine. Each component is responsible for:
//!
//! - Computing residuals based on current system state
//! - Providing Jacobian entries for numerical solving
//! - Reporting the number of equations it contributes
//!
//! ## Object Safety
//!
//! The [`Component`] trait is designed to be **object-safe**, meaning it supports
//! dynamic dispatch via `Box<dyn Component>` or `&dyn Component`. This is essential
//! for the solver to work with collections of heterogeneous components.
//!
//! ## Example
//!
//! ```rust
//! use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
//!
//! struct MockComponent {
//!     n_equations: usize,
//! }
//!
//! impl Component for MockComponent {
//!     fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) -> Result<(), ComponentError> {
//!         // Component-specific residual computation
//!         Ok(())
//!     }
//!
//!     fn jacobian_entries(&self, state: &SystemState, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
//!         // Component-specific Jacobian contributions
//!         Ok(())
//!     }
//!
//!     fn n_equations(&self) -> usize {
//!         self.n_equations
//!     }
//!
//!     fn get_ports(&self) -> &[ConnectedPort] {
//!         &[]
//!     }
//! }
//!
//! // Trait object usage
//! let component: Box<dyn Component> = Box::new(MockComponent { n_equations: 3 });
//! ```

#![warn(missing_docs)]
#![warn(rust_2018_idioms)]

pub mod compressor;
pub mod expansion_valve;
pub mod external_model;
pub mod fan;
pub mod flow_boundary;
pub mod flow_junction;
pub mod heat_exchanger;
pub mod pipe;
pub mod polynomials;
pub mod port;
pub mod pump;
pub mod state_machine;

pub use compressor::{Ahri540Coefficients, Compressor, CompressorModel, SstSdtCoefficients};
pub use expansion_valve::{ExpansionValve, PhaseRegion};
pub use external_model::{
    ExternalModel, ExternalModelConfig, ExternalModelError, ExternalModelMetadata,
    ExternalModelType, MockExternalModel, ThreadSafeExternalModel,
};
pub use fan::{Fan, FanCurves};
pub use heat_exchanger::model::FluidState;
pub use heat_exchanger::{
    Condenser, CondenserCoil, Economizer, EpsNtuModel, Evaporator, EvaporatorCoil, ExchangerType,
    FlowConfiguration, HeatExchanger, HeatExchangerBuilder, HeatTransferModel, HxSideConditions,
    LmtdModel,
};
pub use pipe::{friction_factor, roughness, Pipe, PipeGeometry};
pub use polynomials::{AffinityLaws, PerformanceCurves, Polynomial1D, Polynomial2D};
pub use port::{
    validate_port_continuity, Connected, ConnectedPort, ConnectionError, Disconnected, FluidId, Port,
};
pub use flow_boundary::{
    CompressibleSink, CompressibleSource, FlowSink, FlowSource,
    IncompressibleSink, IncompressibleSource,
};
pub use flow_junction::{
    CompressibleMerger, CompressibleSplitter, FlowMerger, FlowSplitter, FluidKind,
    IncompressibleMerger, IncompressibleSplitter,
};
pub use pump::{Pump, PumpCurves};
pub use state_machine::{
    CircuitId, OperationalState, StateHistory, StateManageable, StateTransitionError,
    StateTransitionRecord,
};

use entropyk_core::MassFlow;
use thiserror::Error;

/// Errors that can occur during component operations.
///
/// This enum represents all possible error conditions that components
/// may encounter during computation, providing detailed context for debugging.
#[derive(Error, Debug, Clone, PartialEq)]
pub enum ComponentError {
    /// Invalid state vector dimensions.
    ///
    /// The state vector provided does not have the expected dimensions
    /// for this component's computation.
    #[error("Invalid state vector dimensions: expected at least {expected}, got {actual}")]
    InvalidStateDimensions {
        /// Expected minimum dimension
        expected: usize,
        /// Actual dimension received
        actual: usize,
    },

    /// Invalid residual vector dimensions.
    ///
    /// The residual vector does not match the expected size for this component.
    #[error("Invalid residual vector dimensions: expected {expected}, got {actual}")]
    InvalidResidualDimensions {
        /// Expected dimension (from n_equations)
        expected: usize,
        /// Actual dimension received
        actual: usize,
    },

    /// Numerical computation error.
    ///
    /// Occurs when a numerical operation fails (e.g., division by zero,
    /// logarithm of non-positive number, NaN or infinite results).
    #[error("Numerical error in component computation: {0}")]
    NumericalError(String),

    /// Invalid component state.
    ///
    /// The component is in an invalid state for computation (e.g.,
    /// disconnected ports, uninitialized parameters).
    #[error("Invalid component state: {0}")]
    InvalidState(String),

    /// Invalid state transition.
    ///
    /// The requested state transition is not allowed for this component.
    #[error("Invalid state transition from {from:?} to {to:?}: {reason}")]
    InvalidStateTransition {
        /// State before attempted transition
        from: OperationalState,
        /// Attempted target state
        to: OperationalState,
        /// Reason for rejection
        reason: String,
    },
    
    /// Calculation dynamically failed.
    ///
    /// Occurs when an underlying model or backend fails to evaluate
    /// properties at the requested state.
    #[error("Calculation failed: {0}")]
    CalculationFailed(String),
}

/// Represents the state of the entire thermodynamic system.
///
/// This type will be refined in future iterations as the system architecture
/// evolves. For now, it provides a placeholder for system-wide state information.
pub type SystemState = Vec<f64>;

/// Vector of residual values for equation solving.
///
/// Residuals represent the difference between expected and actual values
/// in the system of equations. The solver aims to drive all residuals to zero.
pub type ResidualVector = Vec<f64>;

/// Builder for constructing Jacobian matrix entries.
///
/// The Jacobian matrix contains partial derivatives of residuals with respect
/// to state variables. This builder accumulates entries in coordinate format
/// (row, column, value) before assembly into a sparse matrix.
#[derive(Debug, Default)]
pub struct JacobianBuilder {
    entries: Vec<(usize, usize, f64)>,
}

impl JacobianBuilder {
    /// Creates a new empty Jacobian builder.
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::JacobianBuilder;
    ///
    /// let builder = JacobianBuilder::new();
    /// ```
    pub fn new() -> Self {
        Self {
            entries: Vec::new(),
        }
    }

    /// Adds a single entry to the Jacobian.
    ///
    /// # Arguments
    ///
    /// * `row` - Row index in the Jacobian matrix (equation index)
    /// * `col` - Column index in the Jacobian matrix (state variable index)
    /// * `value` - Partial derivative value ∂residual/∂state
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::JacobianBuilder;
    ///
    /// let mut builder = JacobianBuilder::new();
    /// builder.add_entry(0, 1, 2.5);
    /// ```
    pub fn add_entry(&mut self, row: usize, col: usize, value: f64) {
        self.entries.push((row, col, value));
    }

    /// Adds multiple entries at once.
    ///
    /// # Arguments
    ///
    /// * `entries` - Iterator of (row, col, value) tuples
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::JacobianBuilder;
    ///
    /// let mut builder = JacobianBuilder::new();
    /// builder.add_entries(vec![(0, 0, 1.0), (0, 1, 2.0)]);
    /// ```
    pub fn add_entries(&mut self, entries: impl IntoIterator<Item = (usize, usize, f64)>) {
        self.entries.extend(entries);
    }

    /// Returns all accumulated entries.
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::JacobianBuilder;
    ///
    /// let mut builder = JacobianBuilder::new();
    /// builder.add_entry(0, 0, 1.0);
    /// let entries = builder.entries();
    /// assert_eq!(entries.len(), 1);
    /// ```
    pub fn entries(&self) -> &[(usize, usize, f64)] {
        &self.entries
    }

    /// Clears all entries from the builder.
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::JacobianBuilder;
    ///
    /// let mut builder = JacobianBuilder::new();
    /// builder.add_entry(0, 0, 1.0);
    /// builder.clear();
    /// assert!(builder.entries().is_empty());
    /// ```
    pub fn clear(&mut self) {
        self.entries.clear();
    }

    /// Returns the number of accumulated entries.
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::JacobianBuilder;
    ///
    /// let mut builder = JacobianBuilder::new();
    /// builder.add_entry(0, 0, 1.0);
    /// assert_eq!(builder.len(), 1);
    /// ```
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// Returns true if no entries have been added.
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::JacobianBuilder;
    ///
    /// let builder = JacobianBuilder::new();
    /// assert!(builder.is_empty());
    /// ```
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }
}

/// Core trait for all thermodynamic components.
///
/// The `Component` trait defines the interface between thermodynamic components
/// (compressors, heat exchangers, valves, etc.) and the solver engine. All
/// components in the system must implement this trait.
///
/// # Object Safety
///
/// This trait is **object-safe**, meaning it can be used with dynamic dispatch:
///
/// ```
/// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
///
/// struct SimpleComponent;
/// impl Component for SimpleComponent {
///     fn compute_residuals(&self, _state: &SystemState, _residuals: &mut ResidualVector) -> Result<(), ComponentError> {
///         Ok(())
///     }
///     fn jacobian_entries(&self, _state: &SystemState, _jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
///         Ok(())
///     }
///     fn n_equations(&self) -> usize { 1 }
///     fn get_ports(&self) -> &[ConnectedPort] { &[] }
/// }
///
/// let component: Box<dyn Component> = Box::new(SimpleComponent);
/// ```
///
/// # Required Methods
///
/// Implementors must provide three methods:
///
/// - [`compute_residuals`](Self::compute_residuals): Compute residual values
///   representing the component's contribution to the system of equations.
///
/// - [`jacobian_entries`](Self::jacobian_entries): Provide partial derivatives
///   of residuals with respect to state variables.
///
/// - [`n_equations`](Self::n_equations): Report how many equations this
///   component contributes to the overall system.
///
/// # Error Handling
///
/// Both computation methods return [`Result`] to allow components to report
/// errors such as invalid state dimensions, numerical issues, or invalid
/// component configuration.
///
/// # Type Parameters
///
/// Currently, this trait uses simple slice types for state and residuals.
/// Future iterations may introduce generic type parameters for enhanced
/// type safety and performance.
pub trait Component {
    /// Computes residual values for this component.
    ///
    /// Residuals represent the difference between the component's expected
    /// behavior and its actual state. The solver attempts to drive all
    /// residuals to zero.
    ///
    /// # Arguments
    ///
    /// * `state` - Current state vector of the entire system
    /// * `residuals` - Mutable slice to store computed residual values
    ///
    /// # Returns
    ///
    /// Returns `Ok(())` on success, or a [`ComponentError`] if computation fails.
    ///
    /// # Implementation Notes
    ///
    /// The `residuals` slice has length equal to [`n_equations`](Self::n_equations).
    /// Each index corresponds to a specific equation for this component.
    ///
    /// # Example
    ///
    /// ```
    /// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
    ///
    /// struct MassBalanceComponent;
    /// impl Component for MassBalanceComponent {
    ///     fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) -> Result<(), ComponentError> {
    ///         // Validate dimensions
    ///         if state.len() < 2 {
    ///             return Err(ComponentError::InvalidStateDimensions { expected: 2, actual: state.len() });
    ///         }
    ///         // Mass balance: inlet - outlet = 0
    ///         residuals[0] = state[0] - state[1];
    ///         Ok(())
    ///     }
    ///
    ///     fn jacobian_entries(&self, _state: &SystemState, _jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
    ///         Ok(())
    ///     }
    ///     fn n_equations(&self) -> usize { 1 }
    ///     fn get_ports(&self) -> &[ConnectedPort] { &[] }
    /// }
    /// ```
    fn compute_residuals(
        &self,
        state: &SystemState,
        residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError>;

    /// Provides Jacobian matrix entries for this component.
    ///
    /// The Jacobian contains partial derivatives of residuals with respect
    /// to state variables: J[i,j] = ∂residual[i]/∂state[j]
    ///
    /// # Arguments
    ///
    /// * `state` - Current state vector of the entire system
    /// * `jacobian` - Builder for accumulating Jacobian entries
    ///
    /// # Returns
    ///
    /// Returns `Ok(())` on success, or a [`ComponentError`] if computation fails.
    ///
    /// # Implementation Notes
    ///
    /// Use [`JacobianBuilder::add_entry`] to add individual partial derivatives.
    /// Only add non-zero entries to optimize sparse matrix storage.
    ///
    /// # Example
    ///
    /// ```
    /// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
    ///
    /// struct LinearComponent;
    /// impl Component for LinearComponent {
    ///     fn compute_residuals(&self, _state: &SystemState, _residuals: &mut ResidualVector) -> Result<(), ComponentError> {
    ///         Ok(())
    ///     }
    ///
    ///     fn jacobian_entries(&self, _state: &SystemState, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
    ///         // ∂r₀/∂s₀ = 2.0
    ///         jacobian.add_entry(0, 0, 2.0);
    ///         // ∂r₀/∂s₁ = -1.0
    ///         jacobian.add_entry(0, 1, -1.0);
    ///         Ok(())
    ///     }
    ///
    ///     fn n_equations(&self) -> usize { 1 }
    ///     fn get_ports(&self) -> &[ConnectedPort] { &[] }
    /// }
    /// ```
    fn jacobian_entries(
        &self,
        state: &SystemState,
        jacobian: &mut JacobianBuilder,
    ) -> Result<(), ComponentError>;

    /// Returns the number of equations contributed by this component.
    ///
    /// This determines the size of the residual vector passed to
    /// [`compute_residuals`](Self::compute_residuals).
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
    ///
    /// struct ThreeEquationComponent;
    /// impl Component for ThreeEquationComponent {
    ///     fn compute_residuals(&self, _state: &SystemState, _residuals: &mut ResidualVector) -> Result<(), ComponentError> {
    ///         Ok(())
    ///     }
    ///     fn jacobian_entries(&self, _state: &SystemState, _jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
    ///         Ok(())
    ///     }
    ///     fn n_equations(&self) -> usize { 3 }
    ///     fn get_ports(&self) -> &[ConnectedPort] { &[] }
    /// }
    ///
    /// let component = ThreeEquationComponent;
    /// assert_eq!(component.n_equations(), 3);
    /// ```
    fn n_equations(&self) -> usize;

    /// Returns the connected ports of this component.
    ///
    /// This method provides access to the component's ports for topology
    /// validation and graph construction. Components without ports should
    /// return an empty slice.
    ///
    /// # Examples
    ///
    /// ```
    /// use entropyk_components::{Component, ComponentError, SystemState, ResidualVector, JacobianBuilder, ConnectedPort};
    ///
    /// struct PortlessComponent;
    /// impl Component for PortlessComponent {
    ///     fn compute_residuals(&self, _state: &SystemState, _residuals: &mut ResidualVector) -> Result<(), ComponentError> {
    ///         Ok(())
    ///     }
    ///     fn jacobian_entries(&self, _state: &SystemState, _jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
    ///         Ok(())
    ///     }
    ///     fn n_equations(&self) -> usize { 0 }
    ///     fn get_ports(&self) -> &[ConnectedPort] { &[] }
    /// }
    ///
    /// let component = PortlessComponent;
    /// assert!(component.get_ports().is_empty());
    /// ```
    fn get_ports(&self) -> &[ConnectedPort];

    /// Injects system-level context into a component during topology finalization.
    ///
    /// Called by [`System::finalize()`] after all edge state indices are computed.
    /// The default implementation is a no-op; override this in components that need
    /// to know their position in the global state vector (e.g. `MacroComponent`).
    ///
    /// # Arguments
    ///
    /// * `state_offset` — The index in the global state vector where this component's
    ///   *internal* state block begins.  For ordinary leaf components this is never
    ///   needed; for `MacroComponent` it replaces the manual `set_global_state_offset`
    ///   call.
    /// * `external_edge_state_indices` — A slice of `(p_idx, h_idx)` pairs for every
    ///   edge incident to this component's node in the parent graph (incoming and
    ///   outgoing), in traversal order.  `MacroComponent` uses these to emit
    ///   port-coupling residuals.
    fn set_system_context(
        &mut self,
        _state_offset: usize,
        _external_edge_state_indices: &[(usize, usize)],
    ) {
        // Default: no-op for all ordinary leaf components.
    }

    /// Returns the number of internal state variables this component maintains.
    ///
    /// The default implementation returns 0, which is correct for all ordinary
    /// leaf components. Hierarchical components (like `MacroComponent`) should
    /// override this to return the size of their internal state block.
    fn internal_state_len(&self) -> usize {
        0
    }

    /// Returns the mass flow vector associated with the component's ports.
    /// 
    /// The returned vector matches the order of ports returned by `get_ports()`.
    /// Positive values indicate flow *into* the component, negative values flow *out*.
    /// 
    /// # Arguments
    /// 
    /// * `state` - The global system state vector
    /// 
    /// # Returns
    /// 
    /// * `Ok(Vec<MassFlow>)` containing the mass flows if calculation is supported
    /// * `Err(ComponentError::NotImplemented)` by default
    fn port_mass_flows(&self, _state: &SystemState) -> Result<Vec<MassFlow>, ComponentError> {
        Err(ComponentError::CalculationFailed("Mass flow calculation not implemented for this component".to_string()))
    }

    /// Injects control variable indices for calibration parameters into a component.
    ///
    /// Called by the solver (e.g. `System::finalize()`) after matching `BoundedVariable`s
    /// to components, so the component can read calibration factors dynamically from
    /// the system state vector.
    fn set_calib_indices(&mut self, _indices: entropyk_core::CalibIndices) {
        // Default: no-op for components that don't support inverse calibration
    }
}

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

    /// Mock component for testing trait object safety
    struct MockComponent {
        n_equations: usize,
    }

    impl Component for MockComponent {
        fn compute_residuals(
            &self,
            state: &SystemState,
            residuals: &mut ResidualVector,
        ) -> Result<(), ComponentError> {
            // Validate dimensions
            if residuals.len() != self.n_equations {
                return Err(ComponentError::InvalidResidualDimensions {
                    expected: self.n_equations,
                    actual: residuals.len(),
                });
            }

            for (i, residual) in residuals.iter_mut().enumerate() {
                *residual = state.get(i).copied().unwrap_or(0.0);
            }
            Ok(())
        }

        fn jacobian_entries(
            &self,
            _state: &SystemState,
            jacobian: &mut JacobianBuilder,
        ) -> Result<(), ComponentError> {
            // Add identity-like entries for testing
            for i in 0..self.n_equations {
                jacobian.add_entry(i, i, 1.0);
            }
            Ok(())
        }

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

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

    #[test]
    fn test_component_trait_object_compiles() {
        // This test verifies that Component trait is object-safe
        let component: Box<dyn Component> = Box::new(MockComponent { n_equations: 3 });
        assert_eq!(component.n_equations(), 3);
    }

    #[test]
    fn test_component_reference_trait_object() {
        // Test with reference trait object
        let mock = MockComponent { n_equations: 2 };
        let component: &dyn Component = &mock;
        assert_eq!(component.n_equations(), 2);
    }

    #[test]
    fn test_compute_residuals() {
        let component = MockComponent { n_equations: 3 };
        let state = vec![1.0, 2.0, 3.0];
        let mut residuals = vec![0.0; 3];

        let result = component.compute_residuals(&state, &mut residuals);

        assert!(result.is_ok());
        assert_eq!(residuals, vec![1.0, 2.0, 3.0]);
    }

    #[test]
    fn test_compute_residuals_with_wrong_residual_size() {
        let component = MockComponent { n_equations: 3 };
        let state = vec![1.0, 2.0, 3.0];
        let mut residuals = vec![0.0; 2]; // Wrong size

        let result = component.compute_residuals(&state, &mut residuals);

        assert!(result.is_err());
        match result {
            Err(ComponentError::InvalidResidualDimensions { expected, actual }) => {
                assert_eq!(expected, 3);
                assert_eq!(actual, 2);
            }
            _ => panic!("Expected InvalidResidualDimensions error"),
        }
    }

    #[test]
    fn test_compute_residuals_with_empty_state() {
        let component = MockComponent { n_equations: 3 };
        let state: Vec<f64> = vec![];
        let mut residuals = vec![0.0; 3];

        // Should still work, using unwrap_or(0.0) for missing state values
        let result = component.compute_residuals(&state, &mut residuals);

        assert!(result.is_ok());
        assert_eq!(residuals, vec![0.0, 0.0, 0.0]); // Defaults to 0.0
    }

    #[test]
    fn test_compute_residuals_with_zero_equations() {
        let component = MockComponent { n_equations: 0 };
        let state = vec![1.0, 2.0, 3.0];
        let mut residuals: Vec<f64> = vec![];

        let result = component.compute_residuals(&state, &mut residuals);

        assert!(result.is_ok());
        assert!(residuals.is_empty());
    }

    #[test]
    fn test_jacobian_entries() {
        let component = MockComponent { n_equations: 2 };
        let state = vec![0.0; 2];
        let mut jacobian = JacobianBuilder::new();

        let result = component.jacobian_entries(&state, &mut jacobian);

        assert!(result.is_ok());
        let entries = jacobian.entries();
        assert_eq!(entries.len(), 2);
        assert_eq!(entries[0], (0, 0, 1.0));
        assert_eq!(entries[1], (1, 1, 1.0));
    }

    #[test]
    fn test_jacobian_entries_with_zero_equations() {
        let component = MockComponent { n_equations: 0 };
        let state = vec![1.0, 2.0];
        let mut jacobian = JacobianBuilder::new();

        let result = component.jacobian_entries(&state, &mut jacobian);

        assert!(result.is_ok());
        assert!(jacobian.is_empty());
    }

    #[test]
    fn test_jacobian_builder() {
        let mut builder = JacobianBuilder::new();
        assert!(builder.is_empty());

        builder.add_entry(0, 1, 2.5);
        assert_eq!(builder.len(), 1);
        assert!(!builder.is_empty());

        let entries = builder.entries();
        assert_eq!(entries[0], (0, 1, 2.5));
    }

    #[test]
    fn test_jacobian_builder_add_entries() {
        let mut builder = JacobianBuilder::new();
        let entries = vec![(0, 0, 1.0), (0, 1, 2.0), (1, 0, 3.0)];

        builder.add_entries(entries);

        assert_eq!(builder.len(), 3);
        assert_eq!(builder.entries()[0], (0, 0, 1.0));
        assert_eq!(builder.entries()[1], (0, 1, 2.0));
        assert_eq!(builder.entries()[2], (1, 0, 3.0));
    }

    #[test]
    fn test_jacobian_builder_clear() {
        let mut builder = JacobianBuilder::new();
        builder.add_entry(0, 0, 1.0);
        builder.add_entry(1, 1, 2.0);

        assert_eq!(builder.len(), 2);

        builder.clear();

        assert!(builder.is_empty());
        assert_eq!(builder.len(), 0);
    }

    #[test]
    fn test_n_equations() {
        let component = MockComponent { n_equations: 5 };
        assert_eq!(component.n_equations(), 5);
    }

    #[test]
    fn test_n_equations_zero() {
        let component = MockComponent { n_equations: 0 };
        assert_eq!(component.n_equations(), 0);
    }

    #[test]
    fn test_vector_of_components() {
        // Verify we can create a vector of trait objects
        let components: Vec<Box<dyn Component>> = vec![
            Box::new(MockComponent { n_equations: 1 }),
            Box::new(MockComponent { n_equations: 2 }),
            Box::new(MockComponent { n_equations: 3 }),
        ];

        let total_equations: usize = components.iter().map(|c| c.n_equations()).sum();
        assert_eq!(total_equations, 6);
    }

    #[test]
    fn test_vector_with_zero_equation_component() {
        let components: Vec<Box<dyn Component>> = vec![
            Box::new(MockComponent { n_equations: 0 }),
            Box::new(MockComponent { n_equations: 1 }),
            Box::new(MockComponent { n_equations: 0 }),
        ];

        let total_equations: usize = components.iter().map(|c| c.n_equations()).sum();
        assert_eq!(total_equations, 1);
    }

    #[test]
    fn test_component_error_display() {
        let err = ComponentError::InvalidStateDimensions {
            expected: 5,
            actual: 3,
        };
        let msg = format!("{}", err);
        assert!(msg.contains("Invalid state vector dimensions"));
        assert!(msg.contains("expected at least 5"));
        assert!(msg.contains("got 3"));
    }

    #[test]
    fn test_component_error_numerical() {
        let err = ComponentError::NumericalError("Division by zero".to_string());
        let msg = format!("{}", err);
        assert!(msg.contains("Numerical error"));
        assert!(msg.contains("Division by zero"));
    }

    #[test]
    fn test_component_error_invalid_state() {
        let err = ComponentError::InvalidState("Port not connected".to_string());
        let msg = format!("{}", err);
        assert!(msg.contains("Invalid component state"));
        assert!(msg.contains("Port not connected"));
    }

    #[test]
    fn test_error_clonable() {
        let err = ComponentError::InvalidStateDimensions {
            expected: 2,
            actual: 1,
        };
        let cloned = err.clone();
        assert_eq!(err, cloned);
    }

    #[test]
    fn test_component_with_ports_integration() {
        use crate::port::{FluidId, Port};
        use entropyk_core::{Enthalpy, Pressure};

        struct ComponentWithPorts {
            ports: Vec<ConnectedPort>,
        }

        impl ComponentWithPorts {
            fn new() -> Self {
                let port1 = Port::new(
                    FluidId::new("R134a"),
                    Pressure::from_bar(1.0),
                    Enthalpy::from_joules_per_kg(400000.0),
                );
                let port2 = Port::new(
                    FluidId::new("R134a"),
                    Pressure::from_bar(1.0),
                    Enthalpy::from_joules_per_kg(400000.0),
                );
                let (connected1, connected2) =
                    port1.connect(port2).expect("connection should succeed");
                Self {
                    ports: vec![connected1, connected2],
                }
            }
        }

        impl Component for ComponentWithPorts {
            fn compute_residuals(
                &self,
                _state: &SystemState,
                _residuals: &mut ResidualVector,
            ) -> Result<(), ComponentError> {
                Ok(())
            }
            fn jacobian_entries(
                &self,
                _state: &SystemState,
                _jacobian: &mut JacobianBuilder,
            ) -> Result<(), ComponentError> {
                Ok(())
            }
            fn n_equations(&self) -> usize {
                0
            }
            fn get_ports(&self) -> &[ConnectedPort] {
                &self.ports
            }
        }

        let component = ComponentWithPorts::new();
        assert_eq!(component.get_ports().len(), 2);
        assert_eq!(component.get_ports()[0].fluid_id().as_str(), "R134a");

        let boxed: Box<dyn Component> = Box::new(component);
        assert_eq!(boxed.get_ports().len(), 2);
    }
}
