use std::collections::{BTreeMap, BTreeSet};
use super::conflict::Side;
use super::flat::{Flat, Node, Parent};
use super::place::Placed;
use super::tree::Lists;
use crate::identity::Key;
#[derive(Debug, Clone, Copy)]
pub(super) enum Fate {
Kept,
Despite(Side),
Beneath,
Tentative(Side),
Gone,
}
impl Fate {
pub(super) const fn kept(self) -> bool {
matches!(self, Self::Kept | Self::Despite(_) | Self::Beneath)
}
}
pub(super) fn decide(
sides: &[Flat; 3],
places: &BTreeMap<Key, Placed>,
lists: &Lists,
) -> BTreeMap<Key, Fate> {
let keys: BTreeSet<Key> = sides
.iter()
.flat_map(|side| side.nodes.keys().copied())
.collect();
let mut fates: BTreeMap<Key, Fate> = keys
.into_iter()
.map(|key| (key, seed(key, sides, lists)))
.collect();
let parent = |key: &Key| match places.get(key).map(|placed| placed.place.0) {
Some(Parent::Key(parent)) => Some(parent),
_ => None,
};
promote(&mut fates, &parent);
keep_beneath(&mut fates, &parent);
fates
}
fn promote(fates: &mut BTreeMap<Key, Fate>, parent: &dyn Fn(&Key) -> Option<Key>) {
let kept: Vec<Key> = fates
.iter()
.filter(|(_, fate)| fate.kept())
.map(|(key, _)| *key)
.collect();
for key in kept {
let mut at = key;
while let Some(above) = parent(&at) {
let Some(Fate::Tentative(side)) = fates.get(&above).copied() else {
break;
};
fates.insert(above, Fate::Despite(side));
at = above;
}
}
}
fn keep_beneath(fates: &mut BTreeMap<Key, Fate>, parent: &dyn Fn(&Key) -> Option<Key>) {
let holds = |fate: Option<&Fate>| matches!(fate, Some(Fate::Despite(_) | Fate::Beneath));
loop {
let held: Vec<Key> = fates
.iter()
.filter(|(key, fate)| {
matches!(fate, Fate::Tentative(_))
&& parent(key).is_some_and(|above| holds(fates.get(&above)))
})
.map(|(key, _)| *key)
.collect();
if held.is_empty() {
return;
}
fates.extend(held.into_iter().map(|key| (key, Fate::Beneath)));
}
}
fn seed(key: Key, sides: &[Flat; 3], lists: &Lists) -> Fate {
let [base, ours, theirs] = sides.each_ref().map(|side| side.nodes.get(&key));
match (base, ours, theirs) {
(Some(_), Some(_), Some(_)) | (None, _, _) => Fate::Kept,
(Some(_), None, None) => Fate::Gone,
(Some(base), Some(kept), None) => deleted(key, base, kept, Side::Theirs, lists),
(Some(base), None, Some(kept)) => deleted(key, base, kept, Side::Ours, lists),
}
}
fn deleted(key: Key, base: &Node, kept: &Node, deleter: Side, lists: &Lists) -> Fate {
if kept.changed_from(base) || lists.moved(deleter.other(), &kept.place, key) {
Fate::Despite(deleter)
} else {
Fate::Tentative(deleter)
}
}