use alloc::vec::Vec;
use crate::tree::{
cst::IcalCst,
line::IcalLine,
merge::{IcalMerge, IcalMergeAction, IcalMergeReason, IcalPropPath, Op, Slot},
};
pub(super) struct Verdict<'a> {
pub(super) applies: bool,
pub(super) reason: Option<IcalMergeReason<'a>>,
}
impl<'a> IcalMerge<'_, 'a> {
pub(super) fn judge(
&self,
op: &Op<'a>,
left_ops: &[Op<'a>],
right_ops: &[Op<'a>],
) -> Verdict<'a> {
if let Some(collision) = left_ops.iter().find(|left| self.collides(left, op)) {
return Verdict {
applies: collision.scraps(op),
reason: Some(IcalMergeReason::Divergent(collision.action.clone())),
};
}
Verdict {
applies: !left_ops.iter().any(|held| self.agrees(held, op)),
reason: left_ops
.iter()
.find(|left| {
left.across_the_series(op)
&& !right_ops.iter().any(|held| self.agrees(left, held))
})
.map(|left| IcalMergeReason::Recurrence(left.action.clone())),
}
}
fn collides(&self, left: &Op<'a>, right: &Op<'a>) -> bool {
if self.agrees(left, right) {
return false;
}
match (&left.slot, &right.slot) {
(Slot::Component, Slot::Component) => left.reaches(right) || right.reaches(left),
(Slot::Component, _) => left.reaches(right),
(_, Slot::Component) => right.reaches(left),
_ if !same_prop(left.prop(), right.prop()) => false,
_ if left.is_addition() != right.is_addition() => false,
(Slot::Prop, _) | (_, Slot::Prop) => true,
(Slot::Value, Slot::Items) | (Slot::Items, Slot::Value) => true,
(Slot::Param { name, .. }, Slot::Value | Slot::Items)
| (Slot::Value | Slot::Items, Slot::Param { name, .. })
if name == "VALUE" =>
{
true
}
(Slot::Items, _) | (_, Slot::Items) => false,
(
Slot::Param {
name: left,
at: one,
},
Slot::Param {
name: right,
at: two,
},
) => left == right && one == two,
(Slot::Param { .. }, _) | (_, Slot::Param { .. }) => false,
_ => true,
}
}
fn agrees(&self, left: &Op<'a>, right: &Op<'a>) -> bool {
left.action.same_change_as(&right.action) && self.wrote_alike(left, right)
}
fn wrote_alike(&self, left: &Op<'a>, right: &Op<'a>) -> bool {
match &right.action {
IcalMergeAction::ComponentAdded { at } => {
let held = self.left.at(at).map(IcalCst::to_bytes);
held.is_some() && held == self.right.at(at).map(IcalCst::to_bytes)
}
IcalMergeAction::PropAdded { .. } => {
let held = self.added_line(self.left, left);
held.is_some() && held == self.added_line(self.right, right)
}
IcalMergeAction::ComponentRemoved { .. }
| IcalMergeAction::PropRemoved { .. }
| IcalMergeAction::ValueItemRemoved { .. }
| IcalMergeAction::ParamRemoved { .. } => true,
IcalMergeAction::ValueChanged { .. } => {
let (Some(ours), Some(theirs)) = self.written_lines(left, right) else {
return false;
};
ours.value.raw_bytes() == theirs.value.raw_bytes()
}
IcalMergeAction::ValueItemAdded { item, .. } => {
let (Some(ours), Some(theirs)) = self.written_lines(left, right) else {
return false;
};
ours.value.same_item_as(&theirs.value, item)
}
IcalMergeAction::ParamAdded { param, .. }
| IcalMergeAction::ParamChanged { new: param, .. } => {
let (Some(ours), Some(theirs)) = self.written_lines(left, right) else {
return false;
};
ours.same_param_bytes_as(&left.slot, theirs, &right.slot, param)
}
}
}
fn written_lines(
&self,
left: &Op<'a>,
right: &Op<'a>,
) -> (Option<&IcalLine<'a>>, Option<&IcalLine<'a>>) {
(
self.written_line(self.left, left),
self.written_line(self.right, right),
)
}
pub(super) fn written_line<'c>(
&self,
cst: &'c IcalCst<'a>,
op: &Op<'a>,
) -> Option<&'c IcalLine<'a>> {
let at = op.source.as_ref()?;
cst.at(&at.component)?.line_at(at, Some(at.index))
}
fn added_line(&self, cst: &IcalCst<'a>, op: &Op<'a>) -> Option<Vec<u8>> {
let line = self.written_line(cst, op)?;
let mut out = Vec::new();
line.write_bytes(&mut out);
Some(out)
}
}
fn same_prop(left: Option<&IcalPropPath<'_>>, right: Option<&IcalPropPath<'_>>) -> bool {
let (Some(left), Some(right)) = (left, right) else {
return false;
};
if left.component != right.component || !left.name.eq_ignore_ascii_case(&right.name) {
return false;
}
match (&left.identity, &right.identity) {
(Some(left), Some(right)) => left == right,
(None, None) => left.index == right.index,
_ => false,
}
}