use core::iter;
use alloc::{
borrow::Cow,
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use crate::tree::{
cst::{IcalCst, IcalItem},
line::IcalLine,
merge::{IcalComponentPath, IcalMerge, IcalMergeAction, IcalPropPath, Op, Slot},
value::cursor::IcalValueCursor,
};
pub(super) struct Shift<'a> {
removed: Vec<(&'a IcalComponentPath<'a>, String, usize)>,
added: Vec<(&'a IcalComponentPath<'a>, String, usize)>,
}
impl<'a> Shift<'a> {
pub(super) fn of(ops: &'a [Op<'a>]) -> Self {
let mut removed = Vec::new();
let mut added = Vec::new();
for op in ops {
match &op.action {
IcalMergeAction::PropRemoved { at, .. } => {
removed.push((&at.component, at.name.to_ascii_uppercase(), at.index));
}
IcalMergeAction::PropAdded { at, .. } => {
added.push((&at.component, at.name.to_ascii_uppercase(), at.index));
}
_ => {}
}
}
added.sort_by_key(|(_, _, index)| *index);
Self { removed, added }
}
fn translate(&self, at: &IcalPropPath<'_>) -> Option<usize> {
let name = at.name.to_ascii_uppercase();
let mut shift = 0;
for (component, held, index) in &self.removed {
if **component != at.component || *held != name {
continue;
}
if *index == at.index {
return None;
}
if *index < at.index {
shift += 1;
}
}
let mut position = at.index - shift;
for (component, held, index) in &self.added {
if **component != at.component || *held != name {
continue;
}
if *index <= position {
position += 1;
}
}
Some(position)
}
}
#[derive(Default)]
pub(super) struct Restored<'a> {
components: Vec<IcalComponentPath<'a>>,
lines: Vec<(IcalComponentPath<'a>, String, usize)>,
}
impl<'a> Restored<'a> {
fn holds(&self, at: &IcalComponentPath<'a>) -> bool {
self.components.iter().any(|held| at.0.starts_with(&held.0))
}
fn claims(&mut self, at: &IcalPropPath<'a>) -> bool {
let key = (at.component.clone(), at.name.to_ascii_uppercase(), at.index);
if self.lines.contains(&key) {
return false;
}
self.lines.push(key);
true
}
}
impl<'a> IcalMerge<'_, 'a> {
pub(super) fn apply(
&self,
merged: &mut IcalCst<'a>,
op: &Op<'a>,
shift: &Shift<'_>,
restored: &mut Restored<'a>,
) {
let host = match &op.action {
IcalMergeAction::ComponentAdded { at } | IcalMergeAction::ComponentRemoved { at } => {
at.parent()
}
_ => op.path().clone(),
};
if restored.holds(&host) {
return;
}
if merged.at(&host).is_none() {
if !op.action.is_removal() {
self.restore(merged, &host, restored);
}
return;
}
match &op.action {
IcalMergeAction::ComponentAdded { at } => {
let Some(source) = self.right.at(at).cloned() else {
return;
};
let Some(target) = merged.at_mut(&at.parent()) else {
return;
};
target.items.push(IcalItem::Component(Box::new(source)));
}
IcalMergeAction::ComponentRemoved { at } => {
let Some(target) = merged.at_mut(&at.parent()) else {
return;
};
if let Some(held) = target.component_position(at) {
target.items.remove(held);
}
}
_ => self.apply_to_line(merged, op, shift, restored),
}
}
fn restore(
&self,
merged: &mut IcalCst<'a>,
at: &IcalComponentPath<'a>,
restored: &mut Restored<'a>,
) {
let gone = at
.ancestors()
.chain(iter::once(at.clone()))
.find(|path| merged.at(path).is_none());
let Some(gone) = gone else {
return;
};
let Some(source) = self.right.at(&gone).cloned() else {
return;
};
let Some(target) = merged.at_mut(&gone.parent()) else {
return;
};
target.items.push(IcalItem::Component(Box::new(source)));
restored.components.push(gone);
}
fn apply_to_line(
&self,
merged: &mut IcalCst<'a>,
op: &Op<'a>,
shift: &Shift<'_>,
restored: &mut Restored<'a>,
) {
let action = &op.action;
let Some(at) = action.prop_path() else {
return;
};
let source = self.written_line(self.right, op).map(IcalLine::terminated);
let Some(component) = merged.at_mut(&at.component) else {
return;
};
if let IcalMergeAction::PropAdded { .. } = action {
let Some(source) = source else {
return;
};
component.items.push(IcalItem::Prop(source));
return;
}
let target = component.line_ordinal(at, shift.translate(at));
if let IcalMergeAction::PropRemoved { .. } = action {
if let Some(held) =
target.and_then(|ordinal| component.line_position(&at.name, ordinal))
{
component.items.remove(held);
}
return;
}
let Some(source) = source else {
return;
};
let Some(line) = target.and_then(|ordinal| component.nth_line_mut(&at.name, ordinal))
else {
if restored.claims(at) {
component.items.push(IcalItem::Prop(source));
}
return;
};
match action {
IcalMergeAction::ValueChanged { .. } => line.value.clone_from(&source.value),
IcalMergeAction::ValueItemAdded { item, .. } => {
let mut items = line.merge_list();
if items.iter().any(|held| held == item) {
return;
}
items.push(item.to_string());
line.set_merge_list(&items);
}
IcalMergeAction::ValueItemRemoved { item, .. } => {
let mut kept = line.merge_list();
let Some(held) = kept.iter().position(|held| held == item) else {
return;
};
kept.remove(held);
line.set_merge_list(&kept);
}
IcalMergeAction::ParamRemoved { .. } => {
if let Slot::Param { name, at } = &op.slot
&& let Some(held) = line.param_position(name, *at)
{
line.params.remove(held);
}
}
IcalMergeAction::ParamAdded { .. } | IcalMergeAction::ParamChanged { .. } => {
let Slot::Param { name, at } = &op.slot else {
return;
};
let Some(found) = source.param_position(name, *at) else {
return;
};
let written = source.params[found].clone();
match line.param_position(name, *at) {
Some(held) => line.params[held] = written,
None => line.params.push(written),
}
}
_ => {}
}
}
}
impl IcalLine<'_> {
fn merge_list(&mut self) -> Vec<String> {
let items: Vec<String> = IcalValueCursor { line: self }
.list()
.into_iter()
.map(Cow::into_owned)
.collect();
if items.iter().all(|item| item.is_empty()) {
return Vec::new();
}
items
}
fn set_merge_list(&mut self, items: &[String]) {
IcalValueCursor { line: self }.set_list(items);
}
}