use std::collections::BTreeMap;
use net_sdk::deck::DaemonSnapshot;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GroupKind {
Solo,
Replica,
Standby,
Fork { parent_seq: u64 },
}
#[derive(Clone, Copy, Debug)]
pub enum MemberRole {
Solo,
Replica(u16),
StandbyActive,
StandbyWarm(u16),
Fork(u16),
}
pub struct LiveMember<'a> {
pub id: u64,
pub daemon: &'a DaemonSnapshot,
pub role: MemberRole,
}
pub struct LiveGroup<'a> {
pub kind: GroupKind,
pub display_name: String,
pub members: Vec<LiveMember<'a>>,
}
pub fn group_daemons(daemons: &BTreeMap<u64, DaemonSnapshot>) -> Vec<LiveGroup<'_>> {
let mut buckets: BTreeMap<String, Vec<(u64, &DaemonSnapshot)>> = BTreeMap::new();
for (id, d) in daemons {
buckets.entry(d.name.clone()).or_default().push((*id, d));
}
let mut groups = Vec::new();
for (raw_name, mut members) in buckets {
let (kind, display_name) = parse_name(&raw_name);
members.sort_by_key(|(id, _)| *id);
let live_members: Vec<LiveMember<'_>> = members
.into_iter()
.enumerate()
.map(|(i, (id, d))| {
let role = role_for(kind, i);
LiveMember {
id,
daemon: d,
role,
}
})
.collect();
groups.push(LiveGroup {
kind,
display_name,
members: live_members,
});
}
groups.sort_by(|a, b| {
kind_order(&a.kind)
.cmp(&kind_order(&b.kind))
.then_with(|| a.display_name.cmp(&b.display_name))
});
groups
}
fn parse_name(name: &str) -> (GroupKind, String) {
if let Some((display, suffix)) = name.split_once('#') {
if suffix == "replica" {
return (GroupKind::Replica, display.to_string());
}
if suffix == "standby" {
return (GroupKind::Standby, display.to_string());
}
if let Some(seq_str) = suffix.strip_prefix("fork@") {
if let Ok(seq) = seq_str.parse::<u64>() {
return (GroupKind::Fork { parent_seq: seq }, display.to_string());
}
}
}
(GroupKind::Solo, name.to_string())
}
fn role_for(kind: GroupKind, index: usize) -> MemberRole {
let cap = |i: usize| -> u16 { i.min(u16::MAX as usize) as u16 };
match kind {
GroupKind::Solo => MemberRole::Solo,
GroupKind::Replica => MemberRole::Replica(cap(index)),
GroupKind::Fork { .. } => MemberRole::Fork(cap(index)),
GroupKind::Standby => {
if index == 0 {
MemberRole::StandbyActive
} else {
MemberRole::StandbyWarm(cap(index - 1))
}
}
}
}
fn kind_order(k: &GroupKind) -> u8 {
match k {
GroupKind::Solo => 0,
GroupKind::Replica => 1,
GroupKind::Fork { .. } => 2,
GroupKind::Standby => 3,
}
}
pub fn lineage_tag(role: MemberRole, kind: GroupKind) -> String {
match role {
MemberRole::Solo => "SOLO".to_string(),
MemberRole::Replica(i) => format!("REP m[{i}]"),
MemberRole::StandbyActive => "STBY active".to_string(),
MemberRole::StandbyWarm(_) => "STBY warm".to_string(),
MemberRole::Fork(i) => {
if let GroupKind::Fork { parent_seq } = kind {
format!("FORK f[{i}]@{parent_seq}")
} else {
format!("FORK f[{i}]")
}
}
}
}