use anyhow::Result;
use crate::actions::switch_profile;
use crate::lock::with_state_lock;
use crate::profile::{AppConfig, Profile};
pub(crate) const DEFAULT_THRESHOLD: f64 = 95.0;
pub(crate) fn threshold_for(profile: &Profile) -> f64 {
profile.fallback_threshold.unwrap_or(DEFAULT_THRESHOLD)
}
fn is_exhausted(profile: &Profile) -> bool {
let Some(window) = profile.usage.as_ref().and_then(|u| u.five_hour.as_ref()) else {
return false;
};
window.utilization >= threshold_for(profile)
}
fn next_target(config: &AppConfig) -> Option<String> {
let active = config.state.active_profile.as_deref()?;
let chain = &config.state.fallback_chain;
let active_idx = chain.iter().position(|n| n == active)?;
let len = chain.len();
let walk = |accept: &dyn Fn(&Profile) -> bool| -> Option<String> {
for offset in 1..=len {
let candidate = &chain[(active_idx + offset) % len];
if candidate == active {
continue;
}
let Some(profile) = config.find(candidate) else {
continue;
};
if accept(profile) {
return Some(candidate.clone());
}
}
None
};
walk(&|p| !is_exhausted(p)).or_else(|| walk(&|p| threshold_for(p) >= 100.0))
}
pub(crate) fn auto_switch_if_needed(config: &mut AppConfig) -> Result<Option<String>> {
with_state_lock(|| {
let active_name = config.state.active_profile.clone();
let Some(active_name) = active_name else {
return Ok(None);
};
if !config
.state
.fallback_chain
.iter()
.any(|n| n == &active_name)
{
return Ok(None);
}
let Some(active) = config.find(&active_name) else {
return Ok(None);
};
if !is_exhausted(active) {
return Ok(None);
}
let Some(target) = next_target(config) else {
return Ok(None);
};
switch_profile(config, &target)?;
Ok(Some(target))
})
}