//! Integration tests for the Entropyk public API.
//!
//! These tests verify the builder pattern, error propagation, and overall
//! API ergonomics using real component types.

use entropyk::{System, SystemBuilder, ThermoError};
use entropyk_components::{Component, ComponentError, JacobianBuilder, ResidualVector};

struct MockComponent {
    #[allow(dead_code)] // Identifies the fixture instance; not asserted in these tests.
    name: &'static str,
    n_eqs: usize,
}

impl Component for MockComponent {
    fn compute_residuals(
        &self,
        _state: &[f64],
        _residuals: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        Ok(())
    }

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

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

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

#[test]
fn test_builder_creates_empty_system() {
    let builder = SystemBuilder::new();
    assert_eq!(builder.component_count(), 0);
    assert_eq!(builder.edge_count(), 0);
}

#[test]
fn test_builder_adds_components() {
    let builder = SystemBuilder::new()
        .component(
            "comp1",
            Box::new(MockComponent {
                name: "comp1",
                n_eqs: 2,
            }),
        )
        .expect("should add component");

    assert_eq!(builder.component_count(), 1);
}

#[test]
fn test_builder_rejects_duplicate_names() {
    let result = SystemBuilder::new()
        .component(
            "dup",
            Box::new(MockComponent {
                name: "dup",
                n_eqs: 1,
            }),
        )
        .expect("first add should succeed")
        .component(
            "dup",
            Box::new(MockComponent {
                name: "dup",
                n_eqs: 1,
            }),
        );

    assert!(result.is_err());
}

#[test]
fn test_builder_creates_edges() {
    let builder = SystemBuilder::new()
        .component(
            "a",
            Box::new(MockComponent {
                name: "a",
                n_eqs: 1,
            }),
        )
        .expect("add a")
        .component(
            "b",
            Box::new(MockComponent {
                name: "b",
                n_eqs: 1,
            }),
        )
        .expect("add b")
        .edge("a", "b")
        .expect("edge a->b");

    assert_eq!(builder.edge_count(), 1);
}

#[test]
fn test_builder_rejects_missing_edge_component() {
    let result = SystemBuilder::new()
        .component(
            "a",
            Box::new(MockComponent {
                name: "a",
                n_eqs: 1,
            }),
        )
        .expect("add a")
        .edge("a", "nonexistent");

    assert!(result.is_err());
}

#[test]
fn test_builder_into_inner() {
    let system = SystemBuilder::new()
        .component(
            "c",
            Box::new(MockComponent {
                name: "c",
                n_eqs: 1,
            }),
        )
        .expect("add c")
        .into_inner();

    assert_eq!(system.node_count(), 1);
}

#[test]
fn test_direct_system_api() {
    let mut system = System::new();
    let _idx = system.add_component(Box::new(MockComponent {
        name: "test",
        n_eqs: 2,
    }));
    assert_eq!(system.node_count(), 1);
}

#[test]
fn test_error_types_are_compatible() {
    fn _assert_thermo_error_from_component(e: ComponentError) -> ThermoError {
        e.into()
    }
}
