use crate::anchor::AnchorRef;
use crate::event::{Flag, Body, State, Event, Kind, VivacKind};
use std::collections::BTreeMap;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Node {
pub id: String,
pub num: u64,
pub kind: Kind,
pub title: String,
pub why: String,
pub state: State,
pub parent: Option<String>,
pub blocks: bool,
pub note: String,
pub outcome: String,
pub refs: Vec<String>,
pub governs: Vec<String>,
pub opened: String,
pub closed: Option<String>,
pub cierre_forzado: bool,
pub flags: BTreeMap<Flag, String>,
}
#[derive(Debug, Clone)]
pub struct Vivac {
pub id: String,
pub num: u64,
pub kind: VivacKind,
pub stack: Vec<(String, String)>,
pub working_set: Vec<String>,
pub next_intent: String,
pub anchor: AnchorRef,
pub node_ref: Option<String>,
pub label: String,
pub ts: String,
}
impl Vivac {
pub fn alias(&self) -> String {
format!("v{}", self.num)
}
}
impl Node {
pub fn alias(&self) -> String {
format!("{}{}", self.kind.prefix(), self.num)
}
pub fn is_front(&self) -> bool {
self.state.is_open() && self.kind != Kind::Decision
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Counts {
pub total: usize,
pub open_count: usize,
pub closed_count: usize,
pub parked_nodes: usize,
}
impl Counts {
pub fn phrase(&self) -> String {
let mut p = Vec::new();
if self.open_count > 0 {
p.push(format!("{} open", self.open_count));
}
if self.closed_count > 0 {
p.push(format!("{} closed", self.closed_count));
}
if self.parked_nodes > 0 {
p.push(format!("{} parked", self.parked_nodes));
}
p.join(" / ")
}
}
#[derive(Debug, Default)]
pub struct Tree {
nodes: HashMap<String, Node>,
children: HashMap<String, Vec<String>>,
por_num: HashMap<u64, String>,
pub roots: Vec<String>,
pub stack: Vec<String>,
pub vivacs: Vec<Vivac>,
pub next_vivac_num: u64,
pub seq: u64,
pub seq_change: u64,
pub seq_vivac: u64,
pub siguiente_num: u64,
pub broken_lines: usize,
}
pub fn fold(eventos: &[Event], rotas: usize) -> Tree {
let mut a = Tree {
broken_lines: rotas,
..Default::default()
};
for e in eventos {
a.apply(e.seq, &e.ts, &e.payload);
}
a.sort_nodes();
a
}
impl Tree {
pub fn apply(&mut self, seq: u64, ts: &str, cuerpo: &Body) {
self.seq = self.seq.max(seq);
if matches!(cuerpo, Body::VivacCreated { .. }) {
self.seq_vivac = self.seq_vivac.max(seq);
} else {
self.seq_change = self.seq_change.max(seq);
}
match cuerpo {
Body::NodeCreated {
node,
num,
kind,
title,
why,
parent,
blocks,
refs,
governs,
} => {
if self.nodes.contains_key(node) {
return;
}
self.nodes.insert(
node.clone(),
Node {
id: node.clone(),
num: *num,
kind: *kind,
title: title.clone(),
why: why.clone(),
state: State::Active,
parent: parent.clone(),
blocks: *blocks,
note: String::new(),
outcome: String::new(),
refs: refs.clone(),
governs: governs.clone(),
opened: crate::clock::date_of(ts).to_string(),
closed: None,
cierre_forzado: false,
flags: BTreeMap::new(),
},
);
self.por_num.insert(*num, node.clone());
self.siguiente_num = self.siguiente_num.max(*num + 1);
match parent {
Some(p) => self.children.entry(p.clone()).or_default().push(node.clone()),
None => self.roots.push(node.clone()),
}
}
Body::StateChanged {
node,
state,
outcome,
forced,
} => {
if let Some(n) = self.nodes.get_mut(node) {
n.state = *state;
if !outcome.is_empty() {
n.outcome = outcome.clone();
}
n.cierre_forzado = *forced;
n.closed = if state.is_open() {
None
} else {
Some(crate::clock::date_of(ts).to_string())
};
}
}
Body::NodeNoted { node, note } => {
if let Some(n) = self.nodes.get_mut(node) {
n.note = note.clone();
}
}
Body::BlockChanged { node, blocks } => {
if let Some(n) = self.nodes.get_mut(node) {
n.blocks = *blocks;
}
}
Body::Pushed { node } => {
if !self.stack.contains(node) {
self.stack.push(node.clone());
}
}
Body::Popped { node } => {
self.stack.retain(|x| x != node);
}
Body::FlagRaised {
node,
flag,
reason,
} => {
if let Some(n) = self.nodes.get_mut(node) {
n.flags.insert(*flag, reason.clone());
}
}
Body::FlagCleared { node, flag } => {
if let Some(n) = self.nodes.get_mut(node) {
n.flags.remove(flag);
}
}
Body::VivacCreated {
vivac,
num,
kind,
stack,
working_set,
next_intent,
anchor,
node_ref,
label,
} => {
self.next_vivac_num = self.next_vivac_num.max(*num + 1);
self.vivacs.push(Vivac {
id: vivac.clone(),
num: *num,
kind: *kind,
stack: stack.clone(),
working_set: working_set.clone(),
next_intent: next_intent.clone(),
anchor: anchor.clone(),
node_ref: node_ref.clone(),
label: label.clone(),
ts: ts.to_string(),
});
}
Body::Promoted { node } => {
if let Some(n) = self.nodes.get_mut(node) {
n.kind = Kind::Goal;
}
if let Some(i) = self.stack.iter().position(|x| x == node) {
self.stack.drain(..i);
}
}
}
}
pub fn sort_nodes(&mut self) {
let nums: std::collections::HashMap<String, u64> =
self.nodes.iter().map(|(k, n)| (k.clone(), n.num)).collect();
for v in self.children.values_mut() {
v.sort_by_key(|id| nums.get(id).copied().unwrap_or(0));
}
self.roots
.sort_by_key(|id| nums.get(id).copied().unwrap_or(0));
}
}
impl Tree {
pub fn is_empty_tree(&self) -> bool {
self.nodes.is_empty()
}
pub fn total(&self) -> usize {
self.nodes.len()
}
pub fn node(&self, id: &str) -> Option<&Node> {
self.nodes.get(id)
}
pub fn nodes_iter(&self) -> impl Iterator<Item = &Node> {
self.nodes.values()
}
pub fn resolve(&self, s: &str) -> Option<&Node> {
let limpio = s.trim().trim_start_matches('#');
if let Ok(n) = limpio.parse::<u64>() {
return self.por_num.get(&n).and_then(|id| self.nodes.get(id));
}
let sin_prefijo = &limpio[1..];
if limpio.len() > 1 && sin_prefijo.chars().all(|c| c.is_ascii_digit()) {
if let Ok(n) = sin_prefijo.parse::<u64>() {
return self
.por_num
.get(&n)
.and_then(|id| self.nodes.get(id))
.filter(|nd| nd.kind.prefix() == limpio.chars().next().unwrap());
}
}
self.nodes.get(limpio)
}
pub fn children(&self, id: &str) -> Vec<&Node> {
self.children
.get(id)
.map(|v| v.iter().filter_map(|i| self.nodes.get(i)).collect())
.unwrap_or_default()
}
pub fn roots(&self) -> Vec<&Node> {
self.roots
.iter()
.filter_map(|i| self.nodes.get(i))
.collect()
}
pub fn ancestors(&self, id: &str) -> Vec<&Node> {
let mut camino = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut cur = self.nodes.get(id);
while let Some(n) = cur {
if !seen.insert(&n.id) {
break;
}
camino.push(n);
cur = n.parent.as_deref().and_then(|p| self.nodes.get(p));
}
camino.reverse();
camino
}
pub fn descendants(&self, id: &str) -> Vec<&Node> {
let mut out = Vec::new();
let mut stack = vec![id.to_string()];
let mut seen = std::collections::HashSet::new();
while let Some(cur) = stack.pop() {
for h in self.children.get(&cur).map(|v| v.as_slice()).unwrap_or(&[]) {
if seen.insert(h.clone()) {
if let Some(n) = self.nodes.get(h) {
out.push(n);
}
stack.push(h.clone());
}
}
}
out.sort_by_key(|n| n.num);
out
}
pub fn open_blockers(&self, id: &str) -> Vec<&Node> {
self.descendants(id)
.into_iter()
.filter(|n| n.blocks && n.state.is_open())
.collect()
}
pub fn counts(&self, id: &str) -> Counts {
let d = self.descendants(id);
Counts {
total: d.len(),
open_count: d.iter().filter(|n| n.state == State::Active).count(),
closed_count: d.iter().filter(|n| n.state == State::Done).count(),
parked_nodes: d.iter().filter(|n| n.state == State::Suspended).count(),
}
}
pub fn last_vivac(&self) -> Option<&Vivac> {
self.vivacs.last()
}
pub fn vivac(&self, s: &str) -> Option<&Vivac> {
let n: u64 = s.trim().trim_start_matches(['#', 'v']).parse().ok()?;
self.vivacs.iter().find(|v| v.num == n)
}
pub fn focus(&self) -> Option<&Node> {
self.stack.last().and_then(|id| self.nodes.get(id))
}
pub fn stack_depth(&self) -> usize {
self.stack.len()
}
}
#[derive(Debug, Default)]
pub struct Aggregates {
counts: HashMap<String, Counts>,
blockers: HashMap<String, usize>,
pub profundidad_max: usize,
}
impl Aggregates {
pub fn counts(&self, id: &str) -> Counts {
self.counts.get(id).copied().unwrap_or_default()
}
pub fn blockers(&self, id: &str) -> usize {
self.blockers.get(id).copied().unwrap_or(0)
}
}
impl Tree {
pub fn agregados(&self) -> Aggregates {
let mut ag = Aggregates::default();
let mut entradas: Vec<&String> = self.roots.iter().collect();
entradas.extend(
self.nodes
.values()
.filter(|n| {
n.parent
.as_ref()
.is_some_and(|p| !self.nodes.contains_key(p))
})
.map(|n| &n.id),
);
let mut order: Vec<(&String, usize)> = Vec::with_capacity(self.nodes.len());
let mut stack: Vec<(&String, usize)> = entradas.into_iter().map(|id| (id, 1)).collect();
let mut seen = std::collections::HashSet::new();
while let Some((id, depth_of)) = stack.pop() {
if !seen.insert(id) {
continue;
}
ag.profundidad_max = ag.profundidad_max.max(depth_of);
order.push((id, depth_of));
if let Some(hs) = self.children.get(id) {
stack.extend(hs.iter().map(|h| (h, depth_of + 1)));
}
}
for (id, _) in order.iter().rev() {
let mut r = Counts::default();
let mut b = 0usize;
for h in self.children.get(*id).map(|v| v.as_slice()).unwrap_or(&[]) {
let Some(child) = self.nodes.get(h) else {
continue;
};
let hr = ag.counts(h);
r.total += hr.total + 1;
r.open_count += hr.open_count + usize::from(child.state == State::Active);
r.closed_count += hr.closed_count + usize::from(child.state == State::Done);
r.parked_nodes += hr.parked_nodes + usize::from(child.state == State::Suspended);
b += ag.blockers(h) + usize::from(child.blocks && child.state == State::Active);
}
ag.counts.insert((*id).clone(), r);
ag.blockers.insert((*id).clone(), b);
}
ag
}
}