//! Component registry for deserialization from ComponentParams
//!
//! Provides a factory function that reconstructs components from their
//! serialized parameter representation.
//!
//! Components that use the type-state pattern (`Disconnected` → `Connected`)
//! are automatically connected with default port initial conditions.
//! The solver converges regardless of initial values.

use crate::{Component, ComponentParams};

/// Error type for component registry operations
#[derive(Debug, Clone, PartialEq)]
pub enum RegistryError {
    /// Unknown or unsupported component type
    UnknownComponentType(String),
    /// Missing required parameter
    MissingParameter {
        /// Component type that is missing the parameter.
        component: String,
        /// Name of the missing parameter.
        parameter: String,
    },
    /// Invalid parameter value
    InvalidParameter {
        /// Component type that received the invalid parameter.
        component: String,
        /// Name of the invalid parameter.
        parameter: String,
        /// Human-readable reason the parameter value is invalid.
        reason: String,
    },
}

impl std::fmt::Display for RegistryError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RegistryError::UnknownComponentType(t) => {
                write!(f, "Unknown component type: '{}'", t)
            }
            RegistryError::MissingParameter {
                component,
                parameter,
            } => {
                write!(
                    f,
                    "Missing parameter '{}' for component type '{}'",
                    parameter, component
                )
            }
            RegistryError::InvalidParameter {
                component,
                parameter,
                reason,
            } => {
                write!(
                    f,
                    "Invalid parameter '{}' for component type '{}': {}",
                    parameter, component, reason
                )
            }
        }
    }
}

impl std::error::Error for RegistryError {}

/// Returns the component type name from a `ComponentParams`.
pub fn component_type_name(params: &ComponentParams) -> &str {
    &params.component_type
}

// ── Helpers ──────────────────────────────────────────────────────────

fn get_f64(params: &ComponentParams, key: &str) -> Result<f64, RegistryError> {
    match params.get(key) {
        Some(v) => v.as_f64().ok_or_else(|| RegistryError::InvalidParameter {
            component: params.component_type.clone(),
            parameter: key.to_string(),
            reason: "expected a number".to_string(),
        }),
        None => Err(RegistryError::MissingParameter {
            component: params.component_type.clone(),
            parameter: key.to_string(),
        }),
    }
}

fn get_f64_or(params: &ComponentParams, key: &str, default: f64) -> f64 {
    params.get(key).and_then(|v| v.as_f64()).unwrap_or(default)
}

fn get_positive_usize(
    params: &ComponentParams,
    key: &str,
    default: usize,
) -> Result<usize, RegistryError> {
    let val = get_f64_or(params, key, default as f64);
    if !(1.0..=100.0).contains(&val) || val.fract() != 0.0 {
        return Err(RegistryError::InvalidParameter {
            component: params.component_type.clone(),
            parameter: key.to_string(),
            reason: format!("expected a positive integer between 1 and 100, got {}", val),
        });
    }
    Ok(val as usize)
}

fn get_string_or(params: &ComponentParams, key: &str, default: &str) -> String {
    params
        .get(key)
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .unwrap_or_else(|| default.to_string())
}

fn deserialize_field<T: serde::de::DeserializeOwned>(
    params: &ComponentParams,
    key: &str,
) -> Result<T, RegistryError> {
    let val = params
        .get(key)
        .ok_or_else(|| RegistryError::MissingParameter {
            component: params.component_type.clone(),
            parameter: key.to_string(),
        })?;
    serde_json::from_value(val.clone()).map_err(|e| RegistryError::InvalidParameter {
        component: params.component_type.clone(),
        parameter: key.to_string(),
        reason: e.to_string(),
    })
}

fn default_disconnected_port(fluid: &str) -> crate::port::Port<crate::port::Disconnected> {
    use crate::port::{FluidId, Port};
    use entropyk_core::{Enthalpy, Pressure};
    Port::new(
        FluidId::new(fluid),
        Pressure::from_bar(2.0),
        Enthalpy::from_joules_per_kg(400_000.0),
    )
}

fn make_connected_port(fluid: &str, p_pa: f64, h_jkg: f64) -> crate::ConnectedPort {
    use crate::port::{FluidId, Port};
    use entropyk_core::{Enthalpy, Pressure};
    let a = Port::new(
        FluidId::new(fluid),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_jkg),
    );
    let b = Port::new(
        FluidId::new(fluid),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_jkg),
    );
    a.connect(b)
        .expect("port connect with matching params should succeed")
        .0
}

fn reg_err(comp: &str, param: &str, e: impl std::fmt::Display) -> RegistryError {
    RegistryError::InvalidParameter {
        component: comp.to_string(),
        parameter: param.to_string(),
        reason: e.to_string(),
    }
}

/// Reconstructs a component from its serialized parameters.
pub fn create_component(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    match params.component_type.as_str() {
        "Compressor" => create_compressor(params),
        "ExpansionValve" => create_expansion_valve(params),
        "Pump" => create_pump(params),
        "Pipe" => create_pipe(params),
        "Fan" => create_fan(params),
        "Condenser" => create_condenser(params),
        "Evaporator" => create_evaporator(params),
        "Economizer" => create_economizer(params),
        "HeatExchanger" => create_heat_exchanger(params),
        "Node" => create_node(params),
        "Drum" => create_drum(params),
        "FlowSplitter" | "CompressibleSplitter" => create_flow_splitter(params),
        "IncompressibleSplitter" => create_incompressible_splitter(params),
        "FlowMerger" | "CompressibleMerger" => create_flow_merger(params),
        "IncompressibleMerger" => create_incompressible_merger(params),
        "BypassValve" => create_bypass_valve(params),
        "RefrigerantSource" => create_refrigerant_source(params),
        "RefrigerantSink" => create_refrigerant_sink(params),
        "BrineSource" => create_brine_source(params),
        "BrineSink" => create_brine_sink(params),
        "AirSource" => create_air_source(params),
        "AirSink" => create_air_sink(params),
        "FloodedEvaporator" => create_flooded_evaporator(params),
        "FloodedCondenser" => create_flooded_condenser(params),
        "CondenserCoil" => create_condenser_coil(params),
        "EvaporatorCoil" => create_evaporator_coil(params),
        "ScrewEconomizerCompressor" | "ScrewCompressor" => create_screw_compressor(params),
        "CapillaryTube" => create_capillary_tube(params),
        "CentrifugalCompressor" => create_centrifugal_compressor(params),
        _ => Err(RegistryError::UnknownComponentType(
            params.component_type.clone(),
        )),
    }
}

fn create_compressor(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::compressor::{Ahri540Coefficients, Compressor, CompressorModel, SstSdtCoefficients};
    let fluid = get_string_or(params, "fluid", "R134a");
    let speed_rpm = get_f64(params, "speedRpm")?;
    let displacement = get_f64(params, "displacementM3PerRev")?;
    let mech_eff = get_f64(params, "mechanicalEfficiency")?;
    let model_type = get_string_or(params, "modelType", "Ahri540");
    let model = match model_type.as_str() {
        "Ahri540" => CompressorModel::Ahri540(Ahri540Coefficients::new(
            get_f64(params, "m1")?,
            get_f64(params, "m2")?,
            get_f64(params, "m3")?,
            get_f64(params, "m4")?,
            get_f64(params, "m5")?,
            get_f64(params, "m6")?,
            get_f64(params, "m7")?,
            get_f64(params, "m8")?,
            get_f64(params, "m9")?,
            get_f64(params, "m10")?,
        )),
        "SstSdt" => CompressorModel::SstSdt(SstSdtCoefficients::new(
            deserialize_field(params, "massFlowCurve")?,
            deserialize_field(params, "powerCurve")?,
        )),
        other => {
            return Err(reg_err(
                "Compressor",
                "modelType",
                format!("Unknown model type '{other}'"),
            ))
        }
    };
    let disc = Compressor::with_model(
        model,
        default_disconnected_port(&fluid),
        default_disconnected_port(&fluid),
        speed_rpm,
        displacement,
        mech_eff,
    )
    .map_err(|e| reg_err("Compressor", "constructor", e))?;
    let comp = disc
        .connect(
            default_disconnected_port(&fluid),
            default_disconnected_port(&fluid),
        )
        .map_err(|e| reg_err("Compressor", "connect", e))?;
    Ok(Box::new(comp))
}

fn create_expansion_valve(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::expansion_valve::ExpansionValve;
    let fluid = get_string_or(params, "fluid", "R134a");
    let opening = params.get("opening").and_then(|v| v.as_f64());
    let disc = ExpansionValve::new(
        default_disconnected_port(&fluid),
        default_disconnected_port(&fluid),
        opening,
    )
    .map_err(|e| reg_err("ExpansionValve", "constructor", e))?;
    let valve = disc
        .connect(
            default_disconnected_port(&fluid),
            default_disconnected_port(&fluid),
        )
        .map_err(|e| reg_err("ExpansionValve", "connect", e))?;
    Ok(Box::new(valve))
}

fn create_pump(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::polynomials::PerformanceCurves;
    use crate::pump::Pump;
    let fluid = get_string_or(params, "fluid", "Water");
    let density = get_f64_or(params, "fluidDensityKgPerM3", 1000.0);
    let speed_ratio = get_f64_or(params, "speedRatio", 1.0);
    let curves = if let Some(v) = params.get("curves") {
        let perf: PerformanceCurves =
            serde_json::from_value(v.clone()).map_err(|e| reg_err("Pump", "curves", e))?;
        crate::pump::PumpCurves::new(perf).map_err(|e| reg_err("Pump", "curves", e))?
    } else {
        crate::pump::PumpCurves::default()
    };
    let disc = Pump::new(
        curves,
        default_disconnected_port(&fluid),
        default_disconnected_port(&fluid),
        density,
    )
    .map_err(|e| reg_err("Pump", "constructor", e))?;
    let mut pump = disc
        .connect(
            default_disconnected_port(&fluid),
            default_disconnected_port(&fluid),
        )
        .map_err(|e| reg_err("Pump", "connect", e))?;
    pump.set_speed_ratio(speed_ratio)
        .map_err(|e| reg_err("Pump", "speedRatio", e))?;
    Ok(Box::new(pump))
}

fn create_pipe(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::pipe::{Pipe, PipeGeometry};
    let fluid = get_string_or(params, "fluid", "Water");
    let geo = PipeGeometry::new(
        get_f64_or(params, "lengthM", 1.0),
        get_f64_or(params, "diameterM", 0.02),
        get_f64_or(params, "roughnessM", 0.000045),
    )
    .map_err(|e| reg_err("Pipe", "geometry", e))?;
    let disc = Pipe::new(
        geo,
        default_disconnected_port(&fluid),
        default_disconnected_port(&fluid),
        get_f64_or(params, "fluidDensityKgPerM3", 1000.0),
        get_f64_or(params, "fluidViscosityPas", 0.001),
    )
    .map_err(|e| reg_err("Pipe", "constructor", e))?;
    let pipe = disc
        .connect(
            default_disconnected_port(&fluid),
            default_disconnected_port(&fluid),
        )
        .map_err(|e| reg_err("Pipe", "connect", e))?;
    Ok(Box::new(pipe))
}

fn create_fan(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::fan::{Fan, FanCurves};
    use crate::polynomials::PerformanceCurves;
    let air_density = get_f64_or(params, "airDensityKgPerM3", 1.2);
    let speed_ratio = get_f64_or(params, "speedRatio", 1.0);
    let curves = if let Some(v) = params.get("curves") {
        let perf: PerformanceCurves =
            serde_json::from_value(v.clone()).map_err(|e| reg_err("Fan", "curves", e))?;
        FanCurves::new(perf).map_err(|e| reg_err("Fan", "curves", e))?
    } else {
        FanCurves::default()
    };
    let inlet_c = make_connected_port("Air", 101_325.0, 300_000.0);
    let outlet_c = make_connected_port("Air", 101_325.0, 300_000.0);
    let mut fan = Fan::from_connected_parts(curves, inlet_c, outlet_c, air_density)
        .map_err(|e| reg_err("Fan", "constructor", e))?;
    fan.set_speed_ratio(speed_ratio)
        .map_err(|e| reg_err("Fan", "speedRatio", e))?;
    Ok(Box::new(fan))
}

fn create_condenser(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::heat_exchanger::condenser::Condenser;
    let ua = get_f64_or(params, "ua", 5000.0);
    let sat = params.get("saturationTempK").and_then(|v| v.as_f64());
    Ok(Box::new(if let Some(s) = sat {
        Condenser::with_saturation_temp(ua, s)
    } else {
        Condenser::new(ua)
    }))
}

fn create_evaporator(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::heat_exchanger::evaporator::Evaporator;
    let ua = get_f64_or(params, "ua", 5000.0);
    let sat = params.get("saturationTempK").and_then(|v| v.as_f64());
    let sh = params.get("superheatTargetK").and_then(|v| v.as_f64());
    Ok(Box::new(match (sat, sh) {
        (Some(s), Some(h)) => Evaporator::with_superheat(ua, s, h),
        (Some(s), _) => Evaporator::with_superheat(ua, s, 5.0),
        _ => Evaporator::new(ua),
    }))
}

fn create_economizer(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::heat_exchanger::economizer::Economizer;
    Ok(Box::new(Economizer::new(get_f64_or(params, "ua", 3000.0))))
}

fn create_heat_exchanger(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::heat_exchanger::exchanger::HeatExchanger;
    use crate::heat_exchanger::lmtd::{FlowConfiguration, LmtdModel};
    let ua = get_f64_or(params, "ua", 5000.0);
    let name = get_string_or(params, "name", "HeatExchanger");
    let flow_config = match get_string_or(params, "flowConfiguration", "CounterFlow").as_str() {
        "ParallelFlow" => FlowConfiguration::ParallelFlow,
        _ => FlowConfiguration::CounterFlow,
    };
    Ok(Box::new(HeatExchanger::new(
        LmtdModel::new(ua, flow_config),
        name,
    )))
}

fn create_node(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::node::Node;
    let fluid = get_string_or(params, "fluid", "R134a");
    let name = get_string_or(params, "name", "node");
    let disc = Node::new(
        name,
        default_disconnected_port(&fluid),
        default_disconnected_port(&fluid),
    );
    let node = disc
        .connect(
            default_disconnected_port(&fluid),
            default_disconnected_port(&fluid),
        )
        .map_err(|e| reg_err("Node", "connect", e))?;
    Ok(Box::new(node))
}

fn create_drum(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::drum::Drum;
    use entropyk_fluids::TestBackend;
    use std::sync::Arc;
    let fluid = get_string_or(params, "fluid", "R134a");
    let backend: Arc<dyn entropyk_fluids::FluidBackend> = Arc::new(TestBackend::new());
    let drum = Drum::new(
        &fluid,
        make_connected_port(&fluid, 200_000.0, 400_000.0),
        make_connected_port(&fluid, 150_000.0, 410_000.0),
        make_connected_port(&fluid, 150_000.0, 200_000.0),
        make_connected_port(&fluid, 150_000.0, 420_000.0),
        backend,
    )
    .map_err(|e| reg_err("Drum", "constructor", e))?;
    Ok(Box::new(drum))
}

fn create_flow_splitter(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::flow_junction::CompressibleSplitter;
    let fluid = get_string_or(params, "fluid", "R134a");
    let n = get_positive_usize(params, "outletCount", 2)?;
    let inlet = make_connected_port(&fluid, 200_000.0, 400_000.0);
    let outlets: Vec<_> = (0..n)
        .map(|_| make_connected_port(&fluid, 200_000.0, 400_000.0))
        .collect();
    let s = CompressibleSplitter::compressible(&fluid, inlet, outlets)
        .map_err(|e| reg_err("FlowSplitter", "constructor", e))?;
    Ok(Box::new(s))
}

fn create_incompressible_splitter(
    params: &ComponentParams,
) -> Result<Box<dyn Component>, RegistryError> {
    use crate::flow_junction::IncompressibleSplitter;
    let fluid = get_string_or(params, "fluid", "Water");
    let n = get_positive_usize(params, "outletCount", 2)?;
    let inlet = make_connected_port(&fluid, 200_000.0, 400_000.0);
    let outlets: Vec<_> = (0..n)
        .map(|_| make_connected_port(&fluid, 200_000.0, 400_000.0))
        .collect();
    let s = IncompressibleSplitter::incompressible(&fluid, inlet, outlets)
        .map_err(|e| reg_err("IncompressibleSplitter", "constructor", e))?;
    Ok(Box::new(s))
}

fn create_flow_merger(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::flow_junction::CompressibleMerger;
    let fluid = get_string_or(params, "fluid", "R134a");
    let n = get_positive_usize(params, "inletCount", 2)?;
    let inlets: Vec<_> = (0..n)
        .map(|_| make_connected_port(&fluid, 200_000.0, 400_000.0))
        .collect();
    let outlet = make_connected_port(&fluid, 200_000.0, 400_000.0);
    let m = CompressibleMerger::compressible(&fluid, inlets, outlet)
        .map_err(|e| reg_err("FlowMerger", "constructor", e))?;
    Ok(Box::new(m))
}

fn create_incompressible_merger(
    params: &ComponentParams,
) -> Result<Box<dyn Component>, RegistryError> {
    use crate::flow_junction::IncompressibleMerger;
    let fluid = get_string_or(params, "fluid", "Water");
    let n = get_positive_usize(params, "inletCount", 2)?;
    let inlets: Vec<_> = (0..n)
        .map(|_| make_connected_port(&fluid, 200_000.0, 400_000.0))
        .collect();
    let outlet = make_connected_port(&fluid, 200_000.0, 400_000.0);
    let m = IncompressibleMerger::incompressible(&fluid, inlets, outlet)
        .map_err(|e| reg_err("IncompressibleMerger", "constructor", e))?;
    Ok(Box::new(m))
}

fn create_bypass_valve(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::bypass_valve::{BypassValve, BypassValveConfig, ValveCharacteristics};
    let min_pos = get_f64_or(params, "minPosition", 0.0);
    let max_pos = get_f64_or(params, "maxPosition", 1.0);
    if min_pos >= max_pos {
        return Err(RegistryError::InvalidParameter {
            component: "BypassValve".to_string(),
            parameter: "minPosition/maxPosition".to_string(),
            reason: format!(
                "minPosition ({}) must be less than maxPosition ({})",
                min_pos, max_pos
            ),
        });
    }
    let characteristics = match get_string_or(params, "characteristics", "Linear").as_str() {
        "EqualPercentage" => ValveCharacteristics::EqualPercentage,
        _ => ValveCharacteristics::Linear,
    };
    let config = BypassValveConfig {
        cv: get_f64_or(params, "cv", 1.0),
        characteristics,
        min_position: min_pos,
        max_position: max_pos,
        nominal_pressure_drop_pa: get_f64_or(params, "nominalPressureDropPa", 10_000.0),
    };
    Ok(Box::new(BypassValve::new(
        &get_string_or(params, "id", "bypass"),
        config,
    )))
}

fn create_refrigerant_source(
    params: &ComponentParams,
) -> Result<Box<dyn Component>, RegistryError> {
    use crate::refrigerant_boundary::RefrigerantSource;
    use entropyk_core::{Pressure, VaporQuality};
    use entropyk_fluids::TestBackend;
    use std::sync::Arc;
    let fluid = get_string_or(params, "fluid", "R134a");
    let p = get_f64_or(params, "pSetPa", 200_000.0);
    let q = get_f64_or(params, "quality", 0.5).clamp(0.0, 1.0);
    let backend: Arc<dyn entropyk_fluids::FluidBackend> = Arc::new(TestBackend::new());
    let outlet = make_connected_port(&fluid, p, 400_000.0);
    let src = RefrigerantSource::new(
        &fluid,
        Pressure::from_pascals(p),
        VaporQuality::from_fraction(q),
        backend,
        outlet,
    )
    .map_err(|e| reg_err("RefrigerantSource", "constructor", e))?;
    Ok(Box::new(src))
}

fn create_refrigerant_sink(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::refrigerant_boundary::RefrigerantSink;
    use entropyk_core::Pressure;
    use entropyk_fluids::TestBackend;
    use std::sync::Arc;
    let fluid = get_string_or(params, "fluid", "R134a");
    let p = get_f64_or(params, "pBackPa", 200_000.0);
    let backend: Arc<dyn entropyk_fluids::FluidBackend> = Arc::new(TestBackend::new());
    let inlet = make_connected_port(&fluid, p, 400_000.0);
    let sink = RefrigerantSink::new(&fluid, Pressure::from_pascals(p), None, backend, inlet)
        .map_err(|e| reg_err("RefrigerantSink", "constructor", e))?;
    Ok(Box::new(sink))
}

fn create_brine_source(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::brine_boundary::BrineSource;
    use entropyk_core::{Concentration, Pressure, Temperature};
    use entropyk_fluids::TestBackend;
    use std::sync::Arc;
    let fluid = get_string_or(params, "fluid", "Water");
    let p = get_f64_or(params, "pressurePa", 200_000.0);
    let t = get_f64_or(params, "temperatureK", 280.0);
    let c = get_f64_or(params, "concentration", 0.0).clamp(0.0, 1.0);
    let backend: Arc<dyn entropyk_fluids::FluidBackend> = Arc::new(TestBackend::new());
    let outlet = make_connected_port(&fluid, p, 50_000.0);
    let src = BrineSource::new(
        &fluid,
        Pressure::from_pascals(p),
        Temperature::from_kelvin(t),
        Concentration::from_fraction(c),
        backend,
        outlet,
    )
    .map_err(|e| reg_err("BrineSource", "constructor", e))?;
    Ok(Box::new(src))
}

fn create_brine_sink(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::brine_boundary::BrineSink;
    use entropyk_core::Pressure;
    use entropyk_fluids::TestBackend;
    use std::sync::Arc;
    let fluid = get_string_or(params, "fluid", "Water");
    let p = get_f64_or(params, "pressurePa", 200_000.0);
    let backend: Arc<dyn entropyk_fluids::FluidBackend> = Arc::new(TestBackend::new());
    let inlet = make_connected_port(&fluid, p, 50_000.0);
    let sink = BrineSink::new(
        &fluid,
        Pressure::from_pascals(p),
        None,
        None,
        backend,
        inlet,
    )
    .map_err(|e| reg_err("BrineSink", "constructor", e))?;
    Ok(Box::new(sink))
}

fn create_air_source(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::air_boundary::AirSource;
    use entropyk_core::{Pressure, RelativeHumidity, Temperature};
    let t = get_f64_or(params, "dryBulbTempK", 293.15);
    let rh = get_f64_or(params, "relativeHumidity", 0.5).clamp(0.0, 1.0);
    let p = get_f64_or(params, "pressurePa", 101_325.0);
    let outlet = make_connected_port("Air", p, 50_000.0);
    let src = AirSource::from_dry_bulb_rh(
        Temperature::from_kelvin(t),
        RelativeHumidity::from_fraction(rh),
        Pressure::from_pascals(p),
        outlet,
    )
    .map_err(|e| reg_err("AirSource", "constructor", e))?;
    Ok(Box::new(src))
}

fn create_air_sink(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::air_boundary::AirSink;
    use entropyk_core::Pressure;
    let p = get_f64_or(params, "pressurePa", 101_325.0);
    let inlet = make_connected_port("Air", p, 50_000.0);
    let sink = AirSink::new(Pressure::from_pascals(p), inlet)
        .map_err(|e| reg_err("AirSink", "constructor", e))?;
    Ok(Box::new(sink))
}

fn create_flooded_evaporator(
    params: &ComponentParams,
) -> Result<Box<dyn Component>, RegistryError> {
    use crate::heat_exchanger::flooded_evaporator::FloodedEvaporator;
    let ua = get_f64(params, "ua")?;
    Ok(Box::new(FloodedEvaporator::new(ua)))
}

fn create_flooded_condenser(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::heat_exchanger::flooded_condenser::FloodedCondenser;
    let ua = get_f64(params, "ua")?;
    Ok(Box::new(FloodedCondenser::new(ua)))
}

fn create_condenser_coil(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::heat_exchanger::condenser_coil::CondenserCoil;
    Ok(Box::new(CondenserCoil::new(get_f64_or(
        params, "ua", 5000.0,
    ))))
}

fn create_evaporator_coil(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::heat_exchanger::evaporator_coil::EvaporatorCoil;
    Ok(Box::new(EvaporatorCoil::new(get_f64_or(
        params, "ua", 5000.0,
    ))))
}

fn create_screw_compressor(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::screw_economizer_compressor::{ScrewEconomizerCompressor, ScrewPerformanceCurves};
    let fluid = get_string_or(params, "fluid", "R134a");
    let nominal_freq = params
        .get("nominal_frequency_hz")
        .or_else(|| params.get("nominalFrequencyHz"))
        .and_then(|v| v.as_f64())
        .unwrap_or(50.0);
    let frequency_hz = params
        .get("frequency_hz")
        .or_else(|| params.get("frequencyHz"))
        .and_then(|v| v.as_f64());
    let eff = params
        .get("mechanical_efficiency")
        .or_else(|| params.get("mechanicalEfficiency"))
        .and_then(|v| v.as_f64())
        .unwrap_or(0.85);
    let curves: ScrewPerformanceCurves = if params.get("curves").is_some() {
        deserialize_field(params, "curves")?
    } else {
        use crate::Polynomial2D;
        let eco_fraction = get_f64_or(params, "economizer_fraction", 0.12);
        ScrewPerformanceCurves::with_fixed_eco_fraction(
            Polynomial2D::bilinear(
                get_f64_or(params, "mf_a00", 1.2),
                get_f64_or(params, "mf_a10", 0.003),
                get_f64_or(params, "mf_a01", -0.002),
                get_f64_or(params, "mf_a11", 0.000_01),
            ),
            Polynomial2D::bilinear(
                get_f64_or(params, "pw_b00", 55_000.0),
                get_f64_or(params, "pw_b10", 200.0),
                get_f64_or(params, "pw_b01", -300.0),
                get_f64_or(params, "pw_b11", 0.5),
            ),
            eco_fraction,
        )
    };
    let mut comp = ScrewEconomizerCompressor::new(
        curves,
        &fluid,
        nominal_freq,
        eff,
        make_connected_port(&fluid, 200_000.0, 400_000.0),
        make_connected_port(&fluid, 800_000.0, 440_000.0),
        make_connected_port(&fluid, 400_000.0, 420_000.0),
    )
    .map_err(|e| reg_err("ScrewEconomizerCompressor", "constructor", e))?;
    if let Some(freq) = frequency_hz {
        comp.set_frequency_hz(freq)
            .map_err(|e| reg_err("ScrewEconomizerCompressor", "frequency_hz", e))?;
    }
    Ok(Box::new(comp))
}

fn create_capillary_tube(params: &ComponentParams) -> Result<Box<dyn Component>, RegistryError> {
    use crate::capillary_tube::{CapillaryGeometry, CapillaryTube};
    let fluid = get_string_or(params, "fluid", "R134a");
    let geom = CapillaryGeometry {
        diameter_m: get_f64_or(params, "diameterM", 0.001),
        length_m: get_f64_or(params, "lengthM", 2.0),
        n_segments: get_f64_or(params, "nSegments", 20.0) as usize,
    };
    let disc = CapillaryTube::new(
        geom,
        default_disconnected_port(&fluid),
        default_disconnected_port(&fluid),
    )
    .map_err(|e| reg_err("CapillaryTube", "constructor", e))?;
    let tube = disc
        .connect(
            default_disconnected_port(&fluid),
            default_disconnected_port(&fluid),
        )
        .map_err(|e| reg_err("CapillaryTube", "connect", e))?;
    Ok(Box::new(tube))
}

fn create_centrifugal_compressor(
    params: &ComponentParams,
) -> Result<Box<dyn Component>, RegistryError> {
    use crate::centrifugal_compressor::{CentrifugalCompressor, CentrifugalMap};
    let fluid = get_string_or(params, "fluid", "R134a");
    let diameter = get_f64_or(params, "diameterM", 0.25);
    let speed = get_f64_or(params, "speedRpm", 9000.0);
    let disc = CentrifugalCompressor::new(
        CentrifugalMap::default_chiller_map(),
        diameter,
        speed,
        default_disconnected_port(&fluid),
        default_disconnected_port(&fluid),
    )
    .map_err(|e| reg_err("CentrifugalCompressor", "constructor", e))?;
    let comp = disc
        .connect(
            default_disconnected_port(&fluid),
            default_disconnected_port(&fluid),
        )
        .map_err(|e| reg_err("CentrifugalCompressor", "connect", e))?;
    Ok(Box::new(comp))
}

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

    #[test]
    fn test_registry_error_display() {
        let err = RegistryError::UnknownComponentType("Foo".to_string());
        assert!(err.to_string().contains("Foo"));
    }

    #[test]
    fn test_component_type_name() {
        assert_eq!(
            component_type_name(&ComponentParams::new("Compressor")),
            "Compressor"
        );
    }

    #[test]
    fn test_unknown_type_error() {
        let result = create_component(&ComponentParams::new("UnknownWidget"));
        assert!(result.is_err());
    }

    #[test]
    fn test_create_expansion_valve() {
        let params = ComponentParams::new("ExpansionValve")
            .with_param("fluid", "R134a")
            .with_param("opening", 0.8);
        let comp = create_component(&params).unwrap();
        assert_eq!(comp.n_equations(), 2);
    }

    #[test]
    fn test_create_capillary_and_centrifugal() {
        let cap = create_component(&ComponentParams::new("CapillaryTube")).unwrap();
        assert_eq!(cap.n_equations(), 2);
        let cent = create_component(&ComponentParams::new("CentrifugalCompressor")).unwrap();
        assert_eq!(cent.n_equations(), 2);
    }

    #[test]
    fn test_create_condenser() {
        let params = ComponentParams::new("Condenser").with_param("ua", 5000.0);
        assert!(create_component(&params).is_ok());
    }

    #[test]
    fn test_create_evaporator() {
        let params = ComponentParams::new("Evaporator").with_param("ua", 3000.0);
        assert!(create_component(&params).is_ok());
    }

    #[test]
    fn test_create_node() {
        let params = ComponentParams::new("Node")
            .with_param("name", "n1")
            .with_param("fluid", "R134a");
        let comp = create_component(&params).unwrap();
        assert_eq!(comp.n_equations(), 0);
    }

    #[test]
    fn test_create_compressor_ahri540() {
        let params = ComponentParams::new("Compressor")
            .with_param("fluid", "R134a")
            .with_param("speedRpm", 2900.0)
            .with_param("displacementM3PerRev", 0.0001)
            .with_param("mechanicalEfficiency", 0.85)
            .with_param("modelType", "Ahri540")
            .with_param("m1", 0.85)
            .with_param("m2", 2.5)
            .with_param("m3", 500.0)
            .with_param("m4", 1500.0)
            .with_param("m5", -2.5)
            .with_param("m6", 1.8)
            .with_param("m7", 600.0)
            .with_param("m8", 1600.0)
            .with_param("m9", -3.0)
            .with_param("m10", 2.0);
        assert!(create_component(&params).is_ok());
    }

    #[test]
    fn test_create_screw_compressor_accepts_cli_params() {
        let params = ComponentParams::new("ScrewCompressor")
            .with_param("fluid", "R134a")
            .with_param("nominal_frequency_hz", 50.0)
            .with_param("frequency_hz", 40.0)
            .with_param("mechanical_efficiency", 0.92)
            .with_param("economizer_fraction", 0.12)
            .with_param("mf_a00", 1.2)
            .with_param("mf_a10", 0.003)
            .with_param("mf_a01", -0.002)
            .with_param("mf_a11", 0.000_01)
            .with_param("pw_b00", 55_000.0)
            .with_param("pw_b10", 200.0)
            .with_param("pw_b01", -300.0)
            .with_param("pw_b11", 0.5);

        let comp = create_component(&params).unwrap();
        assert_eq!(comp.n_equations(), 6);
        assert_eq!(
            comp.port_names(),
            vec![
                "suction".to_string(),
                "discharge".to_string(),
                "economizer".to_string()
            ]
        );
    }
}
