1use std::collections::{BTreeMap, BTreeSet};
3
4use crate::{LayoutDirection, MindMapData, MindMapError, Node, NodeDirection};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub enum Placement {
9 In(String),
11 Before(String),
13 After(String),
15}
16impl Placement {
17 fn target(&self) -> &str {
18 match self {
19 Self::In(id) | Self::Before(id) | Self::After(id) => id,
20 }
21 }
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum Operation {
27 Insert {
29 node: Node,
31 placement: Placement,
33 },
34 InsertParent {
36 target: String,
38 parent: Node,
40 },
41 Remove(Vec<String>),
43 Move {
45 ids: Vec<String>,
47 placement: Placement,
49 },
50 Copy {
52 ids: Vec<String>,
54 placement: Placement,
56 },
57 SetTopic {
59 id: String,
61 topic: String,
63 },
64 SetExpanded {
66 id: String,
68 expanded: bool,
70 },
71 SetLayout(LayoutDirection),
73 SetCompact(bool),
75}
76
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
79pub struct OperationOptions {
80 pub silent: bool,
84}
85
86#[derive(Clone, Debug, PartialEq, Eq)]
88pub enum CoreEvent {
89 Operation(Box<Operation>),
91 Selection(Vec<String>),
93 History {
95 undo: bool,
97 },
98 Refresh,
100}
101
102#[derive(Clone, Debug, Default, PartialEq, Eq)]
104pub struct OperationResult {
105 pub changed: bool,
107 pub ids: Vec<String>,
109 pub events: Vec<CoreEvent>,
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
114struct Snapshot {
115 data: MindMapData,
116 selection: Vec<String>,
117}
118#[derive(Clone, Debug)]
119struct HistoryEntry {
120 before: Snapshot,
121 after: Snapshot,
122}
123
124#[derive(Clone, Debug)]
129pub struct MindMapState {
130 snapshot: Snapshot,
131 parents: BTreeMap<String, Option<String>>,
132 undo: Vec<HistoryEntry>,
133 redo: Vec<HistoryEntry>,
134 history_limit: usize,
135 read_only: bool,
136 revision: u64,
137 next_id: u64,
138}
139impl MindMapState {
140 pub fn new(data: MindMapData) -> Result<Self, MindMapError> {
142 data.validate()?;
143 let mut state = Self {
144 snapshot: Snapshot {
145 data,
146 selection: Vec::new(),
147 },
148 parents: BTreeMap::new(),
149 undo: Vec::new(),
150 redo: Vec::new(),
151 history_limit: 100,
152 read_only: false,
153 revision: 0,
154 next_id: 0,
155 };
156 state.normalize_sides();
157 state.reindex();
158 Ok(state)
159 }
160 pub fn data(&self) -> &MindMapData {
162 &self.snapshot.data
163 }
164 pub fn selection(&self) -> &[String] {
166 &self.snapshot.selection
167 }
168 pub fn node(&self, id: &str) -> Option<&Node> {
170 self.data().node_data.find(id)
171 }
172 pub fn parent_id(&self, id: &str) -> Option<&str> {
174 self.parents.get(id).and_then(Option::as_deref)
175 }
176 pub fn revision(&self) -> u64 {
178 self.revision
179 }
180 pub fn is_read_only(&self) -> bool {
182 self.read_only
183 }
184 pub fn set_read_only(&mut self, value: bool) {
186 if self.read_only != value {
187 self.read_only = value;
188 self.bump();
189 }
190 }
191 pub fn set_history_limit(&mut self, limit: usize) {
193 self.history_limit = limit;
194 if self.undo.len() > limit {
195 self.undo.drain(..self.undo.len() - limit);
196 }
197 if self.redo.len() > limit {
198 self.redo.drain(..self.redo.len() - limit);
199 }
200 }
201 pub fn history_len(&self) -> (usize, usize) {
203 (self.undo.len(), self.redo.len())
204 }
205 pub fn select(&mut self, ids: &[String]) -> Result<Vec<CoreEvent>, MindMapError> {
207 for id in ids {
208 self.require(id)?;
209 }
210 let wanted: BTreeSet<_> = ids.iter().collect();
211 let ordered = ordered_nodes(&self.data().node_data)
212 .into_iter()
213 .filter(|node| wanted.contains(&node.id))
214 .map(|node| node.id.clone())
215 .collect::<Vec<_>>();
216 if ordered == self.snapshot.selection {
217 return Ok(Vec::new());
218 }
219 self.snapshot.selection = ordered.clone();
220 self.bump();
221 Ok(vec![CoreEvent::Selection(ordered)])
222 }
223 pub fn apply(&mut self, operation: Operation) -> Result<OperationResult, MindMapError> {
225 self.apply_with(operation, OperationOptions::default())
226 }
227 pub fn apply_with(
229 &mut self,
230 operation: Operation,
231 options: OperationOptions,
232 ) -> Result<OperationResult, MindMapError> {
233 self.writable()?;
234 match &operation {
235 Operation::Insert { node, .. } | Operation::InsertParent { parent: node, .. } => {
236 node.validate()?
237 }
238 _ => {}
239 }
240 let before = self.snapshot.clone();
241 let mut candidate = before.clone();
242 let mut next_id = self.next_id;
243 let ids = self.mutate(&mut candidate, &operation, &mut next_id)?;
244 candidate.data.validate()?;
245 normalize_sides(&mut candidate.data);
246 if candidate.data == before.data {
247 return Ok(OperationResult::default());
248 }
249 candidate
250 .selection
251 .retain(|id| candidate.data.node_data.find(id).is_some());
252 let selection_changed = before.selection != candidate.selection;
253 self.snapshot = candidate;
254 self.next_id = next_id;
255 self.reindex();
256 self.bump();
257 self.redo.clear();
258 let mut events = Vec::new();
259 if !options.silent {
260 if self.history_limit > 0 {
261 self.undo.push(HistoryEntry {
262 before,
263 after: self.snapshot.clone(),
264 });
265 if self.undo.len() > self.history_limit {
266 self.undo.remove(0);
267 }
268 }
269 events.push(CoreEvent::Operation(Box::new(operation)));
270 }
271 if selection_changed {
272 events.push(CoreEvent::Selection(self.selection().to_vec()));
273 }
274 Ok(OperationResult {
275 changed: true,
276 ids,
277 events,
278 })
279 }
280 pub fn undo(&mut self) -> Result<Vec<CoreEvent>, MindMapError> {
282 self.writable()?;
283 let entry = self.undo.pop().ok_or(MindMapError::NoHistory)?;
284 self.snapshot = entry.before.clone();
285 self.redo.push(entry);
286 self.reindex();
287 self.bump();
288 Ok(vec![
289 CoreEvent::History { undo: true },
290 CoreEvent::Selection(self.selection().to_vec()),
291 ])
292 }
293 pub fn redo(&mut self) -> Result<Vec<CoreEvent>, MindMapError> {
295 self.writable()?;
296 let entry = self.redo.pop().ok_or(MindMapError::NoHistory)?;
297 self.snapshot = entry.after.clone();
298 self.undo.push(entry);
299 self.reindex();
300 self.bump();
301 Ok(vec![
302 CoreEvent::History { undo: false },
303 CoreEvent::Selection(self.selection().to_vec()),
304 ])
305 }
306 pub fn refresh(&mut self, mut data: MindMapData) -> Result<Vec<CoreEvent>, MindMapError> {
308 self.writable()?;
309 data.validate()?;
310 normalize_sides(&mut data);
311 self.snapshot = Snapshot {
312 data,
313 selection: Vec::new(),
314 };
315 self.undo.clear();
316 self.redo.clear();
317 self.reindex();
318 self.bump();
319 Ok(vec![CoreEvent::Refresh, CoreEvent::Selection(Vec::new())])
320 }
321 fn require(&self, id: &str) -> Result<&Node, MindMapError> {
322 self.node(id)
323 .ok_or_else(|| MindMapError::NodeNotFound(id.into()))
324 }
325 fn writable(&self) -> Result<(), MindMapError> {
326 if self.read_only {
327 Err(MindMapError::ReadOnly)
328 } else {
329 Ok(())
330 }
331 }
332 fn bump(&mut self) {
333 self.revision = self.revision.saturating_add(1);
334 }
335 fn normalize_sides(&mut self) {
336 normalize_sides(&mut self.snapshot.data);
337 }
338 fn reindex(&mut self) {
339 self.parents.clear();
340 let mut pending = vec![(&self.snapshot.data.node_data, None)];
341 while let Some((node, parent)) = pending.pop() {
342 self.parents.insert(node.id.clone(), parent);
343 pending.extend(
344 node.children
345 .iter()
346 .map(|child| (child, Some(node.id.clone()))),
347 );
348 }
349 }
350 fn sources(&self, ids: &[String]) -> Result<Vec<String>, MindMapError> {
351 let wanted: BTreeSet<_> = ids.iter().collect();
352 for id in ids {
353 self.require(id)?;
354 if id == &self.data().node_data.id {
355 return Err(MindMapError::RootOperation);
356 }
357 }
358 let mut result = Vec::new();
359 for node in ordered_nodes(&self.data().node_data) {
360 if !wanted.contains(&node.id) {
361 continue;
362 }
363 let mut parent = self.parent_id(&node.id);
364 let mut covered = false;
365 while let Some(id) = parent {
366 if wanted.iter().any(|wanted| wanted.as_str() == id) {
367 covered = true;
368 break;
369 }
370 parent = self.parent_id(id);
371 }
372 if !covered {
373 result.push(node.id.clone());
374 }
375 }
376 Ok(result)
377 }
378 fn mutate(
379 &self,
380 candidate: &mut Snapshot,
381 operation: &Operation,
382 next_id: &mut u64,
383 ) -> Result<Vec<String>, MindMapError> {
384 let root = &mut candidate.data.node_data;
385 match operation {
386 Operation::SetTopic { id, topic } => {
387 if topic.trim().is_empty() {
388 return Err(MindMapError::InvalidOperation(
389 "Topic cannot be empty".into(),
390 ));
391 }
392 root.find_mut(id)
393 .ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?
394 .topic = topic.clone();
395 Ok(Vec::new())
396 }
397 Operation::SetExpanded { id, expanded } => {
398 root.find_mut(id)
399 .ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?
400 .expanded = *expanded;
401 Ok(Vec::new())
402 }
403 Operation::SetLayout(direction) => {
404 candidate.data.direction = *direction;
405 Ok(Vec::new())
406 }
407 Operation::SetCompact(compact) => {
408 candidate.data.compact = *compact;
409 Ok(Vec::new())
410 }
411 Operation::Insert { node, placement } => {
412 let ids = vec![node.id.clone()];
413 insert(root, vec![node.clone()], placement)?;
414 candidate.selection = ids.clone();
415 Ok(ids)
416 }
417 Operation::InsertParent { target, parent } => {
418 self.require(target)?;
419 if !parent.children.is_empty() {
420 return Err(MindMapError::InvalidOperation(
421 "Inserted parent must be a leaf".into(),
422 ));
423 }
424 let parent_id = self.parent_id(target).ok_or(MindMapError::RootOperation)?;
425 let siblings = &mut root
426 .find_mut(parent_id)
427 .ok_or_else(|| MindMapError::NodeNotFound(parent_id.into()))?
428 .children;
429 let index = siblings
430 .iter()
431 .position(|node| &node.id == target)
432 .ok_or_else(|| MindMapError::NodeNotFound(target.clone()))?;
433 let mut wrapper = parent.clone();
434 let original = siblings.remove(index);
435 wrapper.direction = original.direction;
436 wrapper.children.push(original);
437 siblings.insert(index, wrapper);
438 candidate.selection = vec![parent.id.clone()];
439 Ok(candidate.selection.clone())
440 }
441 Operation::Remove(ids) => {
442 let sources = self.sources(ids)?;
443 let fallback = sources
444 .first()
445 .and_then(|id| self.parent_id(id))
446 .map(str::to_owned);
447 for id in &sources {
448 take(root, id).ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?;
449 }
450 if !sources.is_empty() {
451 candidate.selection = fallback.into_iter().collect();
452 }
453 Ok(sources)
454 }
455 Operation::Move { ids, placement } | Operation::Copy { ids, placement } => {
456 let sources = self.sources(ids)?;
457 self.require(placement.target())?;
458 let copying = matches!(operation, Operation::Copy { .. });
459 if !copying {
460 for id in &sources {
461 if self.require(id)?.find(placement.target()).is_some() {
462 return Err(MindMapError::AncestorMove);
463 }
464 }
465 }
466 let mut nodes = Vec::new();
467 let mut used: BTreeSet<String> = ordered_nodes(root)
468 .iter()
469 .map(|node| node.id.clone())
470 .collect();
471 for id in &sources {
472 let mut node = if copying {
473 self.require(id)?.clone()
474 } else {
475 take(root, id).ok_or_else(|| MindMapError::NodeNotFound(id.clone()))?
476 };
477 if copying {
478 regenerate_ids(&mut node, &mut used, next_id)?;
479 }
480 nodes.push(node);
481 }
482 let inserted = nodes.iter().map(|node| node.id.clone()).collect::<Vec<_>>();
483 if !nodes.is_empty() {
484 insert(root, nodes, placement)?;
485 candidate.selection = inserted.clone();
486 }
487 Ok(inserted)
488 }
489 }
490 }
491}
492
493fn ordered_nodes(root: &Node) -> Vec<&Node> {
494 let mut result = Vec::new();
495 let mut pending = vec![root];
496 while let Some(node) = pending.pop() {
497 result.push(node);
498 pending.extend(node.children.iter().rev());
499 }
500 result
501}
502fn take(root: &mut Node, id: &str) -> Option<Node> {
503 if let Some(index) = root.children.iter().position(|node| node.id == id) {
504 return Some(root.children.remove(index));
505 }
506 root.children.iter_mut().find_map(|node| take(node, id))
507}
508fn find_parent_mut<'a>(root: &'a mut Node, id: &str) -> Option<&'a mut Node> {
509 if root.children.iter().any(|node| node.id == id) {
510 return Some(root);
511 }
512 root.children
513 .iter_mut()
514 .find_map(|node| find_parent_mut(node, id))
515}
516fn insert(
517 root: &mut Node,
518 mut nodes: Vec<Node>,
519 placement: &Placement,
520) -> Result<(), MindMapError> {
521 let target = placement.target();
522 match placement {
523 Placement::In(_) => {
524 let parent = root
525 .find_mut(target)
526 .ok_or_else(|| MindMapError::NodeNotFound(target.into()))?;
527 parent.expanded = true;
528 parent.children.append(&mut nodes);
529 }
530 Placement::Before(_) | Placement::After(_) => {
531 if root.id == target {
532 return Err(MindMapError::RootOperation);
533 }
534 let parent = find_parent_mut(root, target)
535 .ok_or_else(|| MindMapError::NodeNotFound(target.into()))?;
536 let index = parent
537 .children
538 .iter()
539 .position(|node| node.id == target)
540 .ok_or_else(|| MindMapError::NodeNotFound(target.into()))?;
541 let side = parent.children.get(index).and_then(|node| node.direction);
542 for node in &mut nodes {
543 node.direction = side;
544 }
545 let offset = usize::from(matches!(placement, Placement::After(_)));
546 parent
547 .children
548 .splice(index + offset..index + offset, nodes);
549 }
550 }
551 Ok(())
552}
553fn regenerate_ids(
554 node: &mut Node,
555 used: &mut BTreeSet<String>,
556 next: &mut u64,
557) -> Result<(), MindMapError> {
558 loop {
559 *next = next
560 .checked_add(1)
561 .ok_or_else(|| MindMapError::InvalidOperation("Generated ID space exhausted".into()))?;
562 let id = format!("dme-{next}");
563 if used.insert(id.clone()) {
564 node.id = id;
565 break;
566 }
567 }
568 for child in &mut node.children {
569 regenerate_ids(child, used, next)?;
570 }
571 Ok(())
572}
573fn normalize_sides(data: &mut MindMapData) {
574 if data.direction != LayoutDirection::Side {
575 return;
576 }
577 let (mut left, mut right) = (0usize, 0usize);
578 for child in &mut data.node_data.children {
579 let side = match child.direction {
580 Some(side) => side,
581 None if left <= right => NodeDirection::Left,
582 None => NodeDirection::Right,
583 };
584 child.direction = Some(side);
585 match side {
586 NodeDirection::Left => left += 1,
587 NodeDirection::Right => right += 1,
588 }
589 }
590}