//! Types to represent energy.

use rust_decimal::Decimal;
use rust_decimal_macros::dec;

use crate::{
    impl_dec_newtype, json,
    money::Cost,
    number::{self, approx_eq_dec, FromDecimal as _, IsZero},
    Money,
};

impl_dec_newtype!(Ampere, "A");
impl_dec_newtype!(Kw, "kW");
impl_dec_newtype!(Kwh, "kWh");

/// A value of kilo watt hours.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Default)]
#[cfg_attr(test, derive(serde::Deserialize))]
pub struct Kwh(Decimal);

impl IsZero for Kwh {
    fn is_zero(&self) -> bool {
        const TOLERANCE: Decimal = dec!(0.001);

        approx_eq_dec(&self.0, &Decimal::ZERO, TOLERANCE)
    }
}

impl Cost for Kwh {
    fn cost(&self, money: Money) -> Money {
        let cost = self.0.saturating_mul(money.into());
        Money::from_decimal(cost)
    }
}

const KILO: Decimal = dec!(1000);

impl Kwh {
    #[must_use]
    pub(crate) const fn zero() -> Self {
        Self(Decimal::ZERO)
    }

    pub fn watt_hours(self) -> Decimal {
        self.0.saturating_mul(KILO)
    }

    #[expect(clippy::missing_panics_doc, reason = "divisor is non-zero")]
    #[expect(clippy::unwrap_used, reason = "divisor is non-zero")]
    pub fn from_watt_hours(num: Decimal) -> Self {
        Self(num.checked_div(KILO).unwrap())
    }
}

/// A value of kilo watts.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub struct Kw(Decimal);

impl IsZero for Kw {
    fn is_zero(&self) -> bool {
        const TOLERANCE: Decimal = dec!(0.001);

        approx_eq_dec(&self.0, &Decimal::ZERO, TOLERANCE)
    }
}

/// A value of amperes.
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub struct Ampere(Decimal);

impl IsZero for Ampere {
    fn is_zero(&self) -> bool {
        const TOLERANCE: Decimal = dec!(0.001);

        approx_eq_dec(&self.0, &Decimal::ZERO, TOLERANCE)
    }
}
