use super::super::{Facts, facts};
use super::Move;
use crate::app::Snapshot;
use crate::board::{BoardRow, Column};
use crate::git_tree::{AgentState, GitTree};
pub fn plan(snap: &Snapshot, fleet: &Facts, rows: &[BoardRow], now: i64) -> Option<Move> {
reap(snap, fleet, rows, now).or_else(|| spawn(snap, fleet, rows))
}
fn reap(snap: &Snapshot, fleet: &Facts, rows: &[BoardRow], now: i64) -> Option<Move> {
let lease = i64::try_from(fleet.lease?.as_secs()).ok()?;
let tree = snap.trees.get(&fleet.workspace)?;
rows.iter().filter(|r| held_here(r, fleet)).find_map(|row| {
let claimant = row.claimant.clone()?;
let idle = quiet_for(tree, row, now)?;
(idle >= lease).then(move || Move::Reap {
row: row.clone(),
claimant,
since: format!("lease expired {} ago", facts::secs_label(idle - lease)),
})
})
}
fn spawn(snap: &Snapshot, fleet: &Facts, rows: &[BoardRow]) -> Option<Move> {
if !fleet.has_room() {
return None;
}
let project = snap.project_name(&fleet.project);
rows.iter()
.find(|r| r.column == Column::Ready && r.project == project)
.map(|row| Move::Spawn { row: row.clone() })
}
fn held_here(row: &BoardRow, fleet: &Facts) -> bool {
row.column == Column::Claimed
&& row.workspace.as_deref() == Some(crate::naming::leaf(&fleet.workspace).as_str())
}
fn quiet_for(tree: &GitTree, row: &BoardRow, now: i64) -> Option<i64> {
let mut newest: Option<i64> = None;
for agent in &tree.agents {
let root = crate::nav::convs::root_of(&tree.agents, &agent.agent_id)?;
if !row.drones.iter().any(|d| d.root_id == root) {
continue;
}
if matches!(agent.state, AgentState::Live | AgentState::InFlight) {
return None;
}
newest = Some(newest.unwrap_or(i64::MIN).max(agent.last_action_unix));
}
newest.map(|last| now.saturating_sub(last))
}