use std::time::Duration;
use bevy_ecs::prelude::*;
use bevy_reflect::Reflect;
use bevy_time::DelayedCommandsExt;
use rig_core::observe::Emitter;
use serde::{Deserialize, Serialize};
use crate::{
agent::{Backoff, RunOf},
bus::{PendingEffect, acquire_hold, release_hold},
};
pub const BACKOFF_OWNER: &str = "rig-ecs/backoff";
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Reflect)]
#[reflect(Component)]
pub struct RetryAttempt(pub usize);
pub fn delay(backoff: &Backoff, attempt: usize) -> Duration {
let factor = 2u32.saturating_pow(attempt.saturating_sub(1).min(31) as u32);
backoff.base.saturating_mul(factor).min(backoff.max)
}
pub fn hold_retries(
mut commands: Commands,
effects: Query<(Entity, &ChildOf), Added<PendingEffect>>,
turns: Query<(&ChildOf, &RetryAttempt)>,
runs: Query<&RunOf>,
backoffs: Query<&Backoff>,
) {
for (effect, turn_of) in &effects {
let Ok((run_of, RetryAttempt(attempt))) = turns.get(turn_of.parent()) else {
continue;
};
let run = run_of.parent();
let Ok(RunOf(agent)) = runs.get(run) else {
continue;
};
let Some(backoff) = backoffs.get(run).ok().or_else(|| backoffs.get(*agent).ok()) else {
continue;
};
let wait = delay(backoff, *attempt);
commands.queue(move |world: &mut World| {
let _ = acquire_hold(world, effect, Emitter::named(BACKOFF_OWNER));
});
commands
.delayed()
.duration(wait)
.queue(move |world: &mut World| {
let _ = release_hold(world, effect, BACKOFF_OWNER);
});
}
}