//! Calibration factors (Calib) for matching simulation to real machine test data.
//!
//! Short name: Calib. Default 1.0 = no correction. Typical range [0.8, 1.2].
//! Refs: Buildings Modelica, EnergyPlus, TRNSYS, TIL Suite, alphaXiv.
//!
//! ## Recommended calibration order
//!
//! To avoid parameter fighting, calibrate in this order:
//! 1. **f_m** — mass flow (compressor power + ṁ measurements)
//! 2. **f_dp** — pressure drops (inlet/outlet pressures)
//! 3. **f_ua** — heat transfer (superheat, subcooling, capacity)
//! 4. **f_power** — compressor power (if f_m insufficient)

use serde::{Deserialize, Serialize};

fn one() -> f64 {
    1.0
}

/// Calibration factors for matching simulation to real machine test data.
///
/// Default 1.0 = no correction. Typical range [0.8, 1.2]. All factors are validated to lie in [0.5, 2.0].
///
/// | Field     | Full name              | Effect                          | Components                  |
/// |-----------|------------------------|---------------------------------|-----------------------------|
/// | `f_m`     | mass flow factor       | ṁ_eff = f_m × ṁ_nominal        | Compressor, Expansion Valve |
/// | `f_dp`    | pressure drop factor   | ΔP_eff = f_dp × ΔP_nominal     | Pipe, Heat Exchanger        |
/// | `f_ua`    | UA factor              | UA_eff = f_ua × UA_nominal      | Evaporator, Condenser       |
/// | `f_power` | power factor           | Ẇ_eff = f_power × Ẇ_nominal    | Compressor                  |
/// | `f_etav`  | volumetric efficiency  | η_v,eff = f_etav × η_v,nominal  | Compressor (displacement)   |
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Calib {
    /// f_m: ṁ_eff = f_m × ṁ_nominal (Compressor, Valve)
    #[serde(default = "one", alias = "calib_flow")]
    pub f_m: f64,
    /// f_dp: ΔP_eff = f_dp × ΔP_nominal (Pipe, HX)
    #[serde(default = "one", alias = "calib_dpr")]
    pub f_dp: f64,
    /// f_ua: UA_eff = f_ua × UA_nominal (Evaporator, Condenser)
    #[serde(default = "one", alias = "calib_ua")]
    pub f_ua: f64,
    /// f_power: Ẇ_eff = f_power × Ẇ_nominal (Compressor)
    #[serde(default = "one")]
    pub f_power: f64,
    /// f_etav: η_v,eff = f_etav × η_v,nominal (Compressor displacement)
    #[serde(default = "one")]
    pub f_etav: f64,
}

impl Default for Calib {
    fn default() -> Self {
        Self {
            f_m: 1.0,
            f_dp: 1.0,
            f_ua: 1.0,
            f_power: 1.0,
            f_etav: 1.0,
        }
    }
}

/// Stores the state vector indices of calibration factors if they are defined as control variables.
///
/// Used for Inverse Control (Story 5.5). If an index is `Some(i)`, the component should
/// read its calibration factor from `state[i]` instead of using its nominal internal value.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CalibIndices {
    /// State index for f_m multiplier
    pub f_m: Option<usize>,
    /// State index for f_dp multiplier
    pub f_dp: Option<usize>,
    /// State index for f_ua multiplier
    pub f_ua: Option<usize>,
    /// State index for f_power multiplier
    pub f_power: Option<usize>,
    /// State index for f_etav multiplier
    pub f_etav: Option<usize>,
}


/// Error returned when a calibration factor is outside the allowed range [0.5, 2.0].
#[derive(Debug, Clone, PartialEq)]
pub struct CalibValidationError {
    /// Factor name (e.g. "f_m")
    pub factor: &'static str,
    /// Value that failed validation
    pub value: f64,
}

impl std::fmt::Display for CalibValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "calib {} = {} is outside allowed range [0.5, 2.0]",
            self.factor, self.value
        )
    }
}

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

const MIN_F: f64 = 0.5;
const MAX_F: f64 = 2.0;

impl Calib {
    /// Validates that all factors lie in [0.5, 2.0]. Returns `Ok(())` or the first invalid factor.
    pub fn validate(&self) -> Result<(), CalibValidationError> {
        let check = |name: &'static str, value: f64| {
            if !(MIN_F..=MAX_F).contains(&value) {
                Err(CalibValidationError {
                    factor: name,
                    value,
                })
            } else {
                Ok(())
            }
        };
        check("f_m", self.f_m)?;
        check("f_dp", self.f_dp)?;
        check("f_ua", self.f_ua)?;
        check("f_power", self.f_power)?;
        check("f_etav", self.f_etav)?;
        Ok(())
    }
}

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

    #[test]
    fn test_calib_default_all_one() {
        let c = Calib::default();
        assert_eq!(c.f_m, 1.0);
        assert_eq!(c.f_dp, 1.0);
        assert_eq!(c.f_ua, 1.0);
        assert_eq!(c.f_power, 1.0);
        assert_eq!(c.f_etav, 1.0);
        assert!(c.validate().is_ok());
    }

    #[test]
    fn test_calib_validation_bounds() {
        let ok = Calib {
            f_m: 0.5,
            f_dp: 1.0,
            f_ua: 2.0,
            f_power: 1.0,
            f_etav: 1.0,
        };
        assert!(ok.validate().is_ok());

        let bad_m = Calib {
            f_m: 0.4,
            ..Default::default()
        };
        let err = bad_m.validate().unwrap_err();
        assert_eq!(err.factor, "f_m");
        assert!((err.value - 0.4).abs() < 1e-9);

        let bad_high = Calib {
            f_ua: 2.1,
            ..Default::default()
        };
        let err2 = bad_high.validate().unwrap_err();
        assert_eq!(err2.factor, "f_ua");
    }

    #[test]
    fn test_calib_json_roundtrip() {
        let c = Calib {
            f_m: 1.1,
            f_dp: 0.9,
            f_ua: 1.0,
            f_power: 1.05,
            f_etav: 1.0,
        };
        let json = serde_json::to_string(&c).unwrap();
        let c2: Calib = serde_json::from_str(&json).unwrap();
        assert_eq!(c, c2);
    }

    #[test]
    fn test_calib_aliases_backward_compat() {
        // calib_flow → f_m
        let json = r#"{"calib_flow": 1.2}"#;
        let c: Calib = serde_json::from_str(json).unwrap();
        assert_eq!(c.f_m, 1.2);
        assert_eq!(c.f_dp, 1.0);
        assert_eq!(c.f_ua, 1.0);
        assert_eq!(c.f_power, 1.0);
        assert_eq!(c.f_etav, 1.0);
    }
}
