use alloc::borrow::Cow;
use core::fmt;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum UserStatsValue {
Number(u64),
Float(f64),
String(Cow<'static, str>),
Ratio(u64, u64),
Percent(f64),
}
impl UserStatsValue {
#[must_use]
pub fn is_numeric(&self) -> bool {
match &self {
Self::Number(_) | Self::Float(_) | Self::Ratio(_, _) | Self::Percent(_) => true,
Self::String(_) => false,
}
}
#[expect(clippy::cast_precision_loss)]
pub fn stats_div(&mut self, divisor: usize) -> Option<Self> {
match self {
Self::Number(x) => Some(Self::Float(*x as f64 / divisor as f64)),
Self::Float(x) => Some(Self::Float(*x / divisor as f64)),
Self::Percent(x) => Some(Self::Percent(*x / divisor as f64)),
Self::Ratio(x, y) => Some(Self::Percent((*x as f64 / divisor as f64) / *y as f64)),
Self::String(_) => None,
}
}
#[expect(clippy::cast_precision_loss)]
pub fn stats_max(&mut self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::Number(x), Self::Number(y)) => {
if y > x {
Some(Self::Number(*y))
} else {
Some(Self::Number(*x))
}
}
(Self::Float(x), Self::Float(y)) => {
if y > x {
Some(Self::Float(*y))
} else {
Some(Self::Float(*x))
}
}
(Self::Ratio(x, a), Self::Ratio(y, b)) => {
let first = *x as f64 / *a as f64;
let second = *y as f64 / *b as f64;
if first > second {
Some(Self::Percent(first))
} else {
Some(Self::Percent(second))
}
}
(Self::Percent(x), Self::Percent(y)) => {
if y > x {
Some(Self::Percent(*y))
} else {
Some(Self::Percent(*x))
}
}
_ => None,
}
}
#[expect(clippy::cast_precision_loss)]
pub fn stats_min(&mut self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::Number(x), Self::Number(y)) => {
if y > x {
Some(Self::Number(*x))
} else {
Some(Self::Number(*y))
}
}
(Self::Float(x), Self::Float(y)) => {
if y > x {
Some(Self::Float(*x))
} else {
Some(Self::Float(*y))
}
}
(Self::Ratio(x, a), Self::Ratio(y, b)) => {
let first = *x as f64 / *a as f64;
let second = *y as f64 / *b as f64;
if first > second {
Some(Self::Percent(second))
} else {
Some(Self::Percent(first))
}
}
(Self::Percent(x), Self::Percent(y)) => {
if y > x {
Some(Self::Percent(*x))
} else {
Some(Self::Percent(*y))
}
}
_ => None,
}
}
#[expect(clippy::cast_precision_loss)]
pub fn stats_add(&mut self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::Number(x), Self::Number(y)) => Some(Self::Number(*x + *y)),
(Self::Float(x), Self::Float(y)) => Some(Self::Float(*x + *y)),
(Self::Percent(x), Self::Percent(y)) => Some(Self::Percent(*x + *y)),
(Self::Ratio(x, a), Self::Ratio(y, b)) => {
let first = *x as f64 / *a as f64;
let second = *y as f64 / *b as f64;
Some(Self::Percent(first + second))
}
(Self::Percent(x), Self::Ratio(y, b)) => {
let ratio = *y as f64 / *b as f64;
Some(Self::Percent(*x + ratio))
}
(Self::Ratio(x, a), Self::Percent(y)) => {
let ratio = *x as f64 / *a as f64;
Some(Self::Percent(ratio + *y))
}
_ => None,
}
}
}
impl fmt::Display for UserStatsValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
UserStatsValue::Number(n) => write!(f, "{n}"),
UserStatsValue::Float(n) => write!(f, "{}", crate::monitors::stats::prettify_float(*n)),
UserStatsValue::Percent(n) => write!(f, "{:.3}%", n * 100.0),
UserStatsValue::String(s) => write!(f, "{s}"),
UserStatsValue::Ratio(a, b) => {
if *b == 0 {
write!(f, "{a}/{b}")
} else {
write!(f, "{a}/{b} ({}%)", a * 100 / b)
}
}
}
}
}