Skip to main content

link_cli/
query_processor.rs

1//! QueryProcessor - Handles LiNo query parsing and execution
2//!
3//! This module provides the QueryProcessor for processing LiNo queries.
4//! Corresponds to BasicQueryProcessor, MixedQueryProcessor, and AdvancedMixedQueryProcessor in C#
5
6use anyhow::Result;
7use std::collections::{HashMap, HashSet};
8
9use crate::changes_simplifier::simplify_changes;
10use crate::error::LinkError;
11use crate::link::Link;
12use crate::link_reference_validator::LinkReferenceValidator;
13use crate::lino_link::LinoLink;
14use crate::named_type_links::NamedTypeLinks;
15use crate::parser::Parser;
16use crate::query_types::{Pattern, ResolvedLink};
17
18/// QueryProcessor handles LiNo query parsing and execution
19/// Corresponds to AdvancedMixedQueryProcessor in C#
20pub struct QueryProcessor {
21    trace: bool,
22    auto_create_missing_references: bool,
23}
24
25impl QueryProcessor {
26    /// Creates a new QueryProcessor
27    pub fn new(trace: bool) -> Self {
28        Self {
29            trace,
30            auto_create_missing_references: false,
31        }
32    }
33
34    pub fn with_auto_create_missing_references(
35        mut self,
36        auto_create_missing_references: bool,
37    ) -> Self {
38        self.auto_create_missing_references = auto_create_missing_references;
39        self
40    }
41
42    /// Processes a LiNo query and returns the list of changes
43    pub fn process_query(
44        &self,
45        storage: &mut impl NamedTypeLinks,
46        query: &str,
47    ) -> Result<Vec<(Option<Link>, Option<Link>)>> {
48        self.trace_msg(&format!("[ProcessQuery] Query: \"{}\"", query));
49
50        let query = query.trim();
51        if query.is_empty() {
52            self.trace_msg("[ProcessQuery] Query is empty, returning.");
53            return Ok(vec![]);
54        }
55
56        let parser = Parser::new();
57        let parsed_links = parser.parse(query)?;
58
59        self.trace_msg(&format!(
60            "[ProcessQuery] Parser returned {} top-level link(s).",
61            parsed_links.len()
62        ));
63
64        if parsed_links.is_empty() {
65            self.trace_msg("[ProcessQuery] No top-level parsed links found, returning.");
66            return Ok(vec![]);
67        }
68
69        // Accept both the wrapped form `((restriction) (substitution))` and
70        // the C# parser-compatible form `restriction substitution`.
71        let (restriction_link, substitution_link) = match &parsed_links[0].values {
72            Some(values) if values.len() >= 2 => (&values[0], &values[1]),
73            _ if parsed_links.len() >= 2 => (&parsed_links[0], &parsed_links[1]),
74            _ => {
75                self.trace_msg("[ProcessQuery] Query has fewer than 2 links, returning.");
76                return Ok(vec![]);
77            }
78        };
79
80        self.trace_msg(&format!(
81            "[ProcessQuery] Restriction link => Id={:?} Values.Count={}",
82            restriction_link.id,
83            restriction_link.values_count()
84        ));
85        self.trace_msg(&format!(
86            "[ProcessQuery] Substitution link => Id={:?} Values.Count={}",
87            substitution_link.id,
88            substitution_link.values_count()
89        ));
90
91        let mut changes_list = Vec::new();
92
93        // If both restriction and substitution are empty, do nothing
94        if restriction_link.is_empty() && substitution_link.is_empty() {
95            self.trace_msg(
96                "[ProcessQuery] Restriction & substitution both empty => no operation, returning.",
97            );
98            return Ok(vec![]);
99        }
100
101        // Creation scenario: no restriction, only substitution
102        if restriction_link.is_empty() && !substitution_link.is_empty() {
103            self.trace_msg(
104                "[ProcessQuery] No restriction, but substitution is non-empty => creation scenario.",
105            );
106            if let Some(values) = &substitution_link.values {
107                changes_list.extend(
108                    self.validate_links_exist_or_will_be_created(storage, &[], values)?
109                        .into_iter()
110                        .map(|link| (None, Some(link))),
111                );
112
113                for link_to_create in values {
114                    let created_id = self.ensure_link_created(storage, link_to_create)?;
115                    self.trace_msg(&format!(
116                        "[ProcessQuery] Created link ID #{} from substitution pattern.",
117                        created_id
118                    ));
119                    if let Some(link) = storage.get_link(created_id) {
120                        changes_list.push((None, Some(link)));
121                    }
122                }
123            }
124            storage.save()?;
125            return Ok(changes_list);
126        }
127
128        // Deletion scenario: restriction but no substitution
129        if !restriction_link.is_empty() && substitution_link.is_empty() {
130            self.trace_msg(
131                "[ProcessQuery] Restriction non-empty, substitution empty => deletion scenario.",
132            );
133            let restriction_values = restriction_link.values.as_deref().unwrap_or(&[]);
134            changes_list.extend(
135                self.validate_links_exist_or_will_be_created(storage, restriction_values, &[])?
136                    .into_iter()
137                    .map(|link| (None, Some(link))),
138            );
139
140            let restriction_patterns = self.patterns_from_lino(restriction_link);
141            let mut links_to_delete = Vec::new();
142            for pattern in &restriction_patterns {
143                links_to_delete.extend(self.matched_links(storage, pattern, &HashMap::new())?);
144            }
145            links_to_delete.sort_by_key(|link| link.index);
146            links_to_delete.dedup_by_key(|link| link.index);
147
148            for link in links_to_delete {
149                if storage.exists(link.index) {
150                    let before = storage.delete(link.index)?;
151                    changes_list.push((Some(before), None));
152                    self.trace_msg(&format!("[ProcessQuery] Deleted link ID #{}.", link.index));
153                }
154            }
155            storage.save()?;
156            return Ok(changes_list);
157        }
158
159        // Update/Mixed scenario: both restriction and substitution have values
160        self.trace_msg(
161            "[ProcessQuery] Both restriction and substitution non-empty => update/mixed scenario.",
162        );
163
164        let restriction_patterns = self.patterns_from_lino(restriction_link);
165        let substitution_patterns = self.patterns_from_lino(substitution_link);
166        let restriction_values = restriction_link.values.as_deref().unwrap_or(&[]);
167        let substitution_values = substitution_link.values.as_deref().unwrap_or(&[]);
168        changes_list.extend(
169            self.validate_links_exist_or_will_be_created(
170                storage,
171                restriction_values,
172                substitution_values,
173            )?
174            .into_iter()
175            .map(|link| (None, Some(link))),
176        );
177        let solutions = self.find_all_solutions(storage, &restriction_patterns)?;
178
179        if solutions.is_empty() {
180            self.trace_msg("[ProcessQuery] No solutions found => returning.");
181            if !changes_list.is_empty() {
182                storage.save()?;
183            }
184            return Ok(changes_list);
185        }
186
187        let mut all_solutions_no_operation = true;
188        for solution in &solutions {
189            if !self.solution_is_no_operation(
190                storage,
191                solution,
192                &restriction_patterns,
193                &substitution_patterns,
194            )? {
195                all_solutions_no_operation = false;
196                break;
197            }
198        }
199
200        if all_solutions_no_operation {
201            for solution in &solutions {
202                for pattern in &restriction_patterns {
203                    for link in self.matched_links(storage, pattern, solution)? {
204                        if !changes_list.contains(&(Some(link), Some(link))) {
205                            changes_list.push((Some(link), Some(link)));
206                        }
207                    }
208                }
209            }
210            return Ok(changes_list);
211        }
212
213        for solution in &solutions {
214            let restriction_links =
215                self.resolve_patterns(storage, &restriction_patterns, solution, false)?;
216            let substitution_links =
217                self.resolve_patterns(storage, &substitution_patterns, solution, true)?;
218            let operations = self.determine_operations(&restriction_links, &substitution_links);
219            for (before, after) in operations {
220                self.apply_operation(storage, before, after, &mut changes_list)?;
221            }
222        }
223
224        storage.save()?;
225
226        // Simplify changes
227        let simplified = self.simplify_changes_list(&changes_list);
228
229        Ok(simplified)
230    }
231
232    fn validate_links_exist_or_will_be_created(
233        &self,
234        storage: &mut impl NamedTypeLinks,
235        restriction_patterns: &[LinoLink],
236        substitution_patterns: &[LinoLink],
237    ) -> Result<Vec<Link>> {
238        LinkReferenceValidator::new(self.trace, self.auto_create_missing_references)
239            .validate_links_exist_or_will_be_created(
240                storage,
241                restriction_patterns,
242                substitution_patterns,
243            )
244    }
245
246    fn patterns_from_lino(&self, lino_link: &LinoLink) -> Vec<Pattern> {
247        let mut patterns = lino_link
248            .values
249            .as_ref()
250            .map(|values| {
251                values
252                    .iter()
253                    .map(Self::create_pattern_from_lino)
254                    .collect::<Vec<_>>()
255            })
256            .unwrap_or_default();
257
258        if lino_link.id.is_some() {
259            patterns.insert(0, Self::create_pattern_from_lino(lino_link));
260        }
261
262        patterns
263    }
264
265    fn create_pattern_from_lino(lino_link: &LinoLink) -> Pattern {
266        let index = lino_link.id.clone().unwrap_or_default();
267        match &lino_link.values {
268            Some(values) if values.len() == 2 => Pattern::new(
269                index,
270                Some(Self::create_pattern_from_lino(&values[0])),
271                Some(Self::create_pattern_from_lino(&values[1])),
272            ),
273            _ => Pattern::new(index, None, None),
274        }
275    }
276
277    fn find_all_solutions(
278        &self,
279        storage: &mut impl NamedTypeLinks,
280        patterns: &[Pattern],
281    ) -> Result<Vec<HashMap<String, u32>>> {
282        let mut partial_solutions = vec![HashMap::new()];
283
284        for pattern in patterns {
285            let mut new_solutions = Vec::new();
286            for solution in &partial_solutions {
287                for match_solution in self.match_pattern(storage, pattern, solution)? {
288                    if Self::solutions_are_compatible(solution, &match_solution) {
289                        let mut combined = solution.clone();
290                        combined.extend(match_solution);
291                        new_solutions.push(combined);
292                    }
293                }
294            }
295            partial_solutions = new_solutions;
296            if partial_solutions.is_empty() {
297                break;
298            }
299        }
300
301        Ok(partial_solutions)
302    }
303
304    fn solutions_are_compatible(
305        existing: &HashMap<String, u32>,
306        new_assignments: &HashMap<String, u32>,
307    ) -> bool {
308        new_assignments
309            .iter()
310            .all(|(key, value)| existing.get(key).is_none_or(|existing| existing == value))
311    }
312
313    fn match_pattern(
314        &self,
315        storage: &mut impl NamedTypeLinks,
316        pattern: &Pattern,
317        current_solution: &HashMap<String, u32>,
318    ) -> Result<Vec<HashMap<String, u32>>> {
319        if pattern.is_leaf() {
320            let resolved_index =
321                self.resolve_match_id(storage, &pattern.index, current_solution)?;
322            return Ok(storage
323                .all_links()
324                .into_iter()
325                .filter(|link| Self::is_any(resolved_index) || link.index == resolved_index)
326                .map(|link| {
327                    let mut assignments = HashMap::new();
328                    Self::assign_variable(&pattern.index, link.index, &mut assignments);
329                    assignments
330                })
331                .collect());
332        }
333
334        let resolved_index = self.resolve_match_id(storage, &pattern.index, current_solution)?;
335
336        if !Self::is_variable(&pattern.index)
337            && !Self::is_any(resolved_index)
338            && resolved_index != 0
339            && storage.exists(resolved_index)
340        {
341            let link = storage.get_link(resolved_index).unwrap();
342            return self.match_link_against_pattern(storage, pattern, link, current_solution);
343        }
344
345        let mut results = Vec::new();
346        for link in storage.all_links() {
347            results.extend(self.match_link_against_pattern(
348                storage,
349                pattern,
350                link,
351                current_solution,
352            )?);
353        }
354        Ok(results)
355    }
356
357    fn match_link_against_pattern(
358        &self,
359        storage: &mut impl NamedTypeLinks,
360        pattern: &Pattern,
361        link: Link,
362        current_solution: &HashMap<String, u32>,
363    ) -> Result<Vec<HashMap<String, u32>>> {
364        if !self.check_id_match(storage, &pattern.index, link.index, current_solution)? {
365            return Ok(Vec::new());
366        }
367
368        let mut results = Vec::new();
369        let source_matches = self.recursive_match_subpattern(
370            storage,
371            pattern.source.as_deref(),
372            link.source,
373            current_solution,
374        )?;
375
376        for source_solution in source_matches {
377            let target_matches = self.recursive_match_subpattern(
378                storage,
379                pattern.target.as_deref(),
380                link.target,
381                &source_solution,
382            )?;
383            for mut target_solution in target_matches {
384                Self::assign_variable(&pattern.index, link.index, &mut target_solution);
385                results.push(target_solution);
386            }
387        }
388
389        Ok(results)
390    }
391
392    fn recursive_match_subpattern(
393        &self,
394        storage: &mut impl NamedTypeLinks,
395        pattern: Option<&Pattern>,
396        link_id: u32,
397        current_solution: &HashMap<String, u32>,
398    ) -> Result<Vec<HashMap<String, u32>>> {
399        let Some(pattern) = pattern else {
400            return Ok(vec![current_solution.clone()]);
401        };
402
403        if pattern.is_leaf() {
404            if self.check_id_match(storage, &pattern.index, link_id, current_solution)? {
405                let mut solution = current_solution.clone();
406                Self::assign_variable(&pattern.index, link_id, &mut solution);
407                return Ok(vec![solution]);
408            }
409            return Ok(Vec::new());
410        }
411
412        let Some(link) = storage.get_link(link_id) else {
413            return Ok(Vec::new());
414        };
415
416        self.match_link_against_pattern(storage, pattern, link, current_solution)
417    }
418
419    fn check_id_match(
420        &self,
421        storage: &mut impl NamedTypeLinks,
422        pattern_id: &str,
423        candidate_id: u32,
424        current_solution: &HashMap<String, u32>,
425    ) -> Result<bool> {
426        if pattern_id.is_empty() || pattern_id == "*" {
427            return Ok(true);
428        }
429
430        if Self::is_variable(pattern_id) {
431            return Ok(current_solution
432                .get(pattern_id)
433                .is_none_or(|existing| *existing == candidate_id));
434        }
435
436        if let Ok(parsed) = pattern_id.parse::<u32>() {
437            return Ok(parsed == candidate_id);
438        }
439
440        Ok(storage
441            .get_by_name(pattern_id)?
442            .is_some_and(|named_id| named_id == candidate_id))
443    }
444
445    fn resolve_match_id(
446        &self,
447        storage: &mut impl NamedTypeLinks,
448        identifier: &str,
449        current_solution: &HashMap<String, u32>,
450    ) -> Result<u32> {
451        if identifier.is_empty() || identifier == "*" {
452            return Ok(u32::MAX);
453        }
454        if let Some(value) = current_solution.get(identifier) {
455            return Ok(*value);
456        }
457        if Self::is_variable(identifier) {
458            return Ok(u32::MAX);
459        }
460        if let Ok(parsed) = identifier.parse::<u32>() {
461            return Ok(parsed);
462        }
463        Ok(storage.get_by_name(identifier)?.unwrap_or(0))
464    }
465
466    fn matched_links(
467        &self,
468        storage: &mut impl NamedTypeLinks,
469        pattern: &Pattern,
470        solution: &HashMap<String, u32>,
471    ) -> Result<Vec<Link>> {
472        if pattern.is_leaf() {
473            let resolved_index = self.resolve_match_id(storage, &pattern.index, solution)?;
474            return Ok(storage
475                .all_links()
476                .into_iter()
477                .filter(|link| Self::is_any(resolved_index) || link.index == resolved_index)
478                .collect());
479        }
480
481        let mut links = Vec::new();
482        for matched_solution in self.match_pattern(storage, pattern, solution)? {
483            if let Some(definition) =
484                self.resolve_pattern_readonly(storage, pattern, &matched_solution, false)?
485            {
486                links.extend(self.links_matching_definition(storage, &definition)?);
487            }
488        }
489        Ok(links)
490    }
491
492    fn solution_is_no_operation(
493        &self,
494        storage: &mut impl NamedTypeLinks,
495        solution: &HashMap<String, u32>,
496        restrictions: &[Pattern],
497        substitutions: &[Pattern],
498    ) -> Result<bool> {
499        let mut restriction_links = self
500            .resolve_patterns_readonly(storage, restrictions, solution, false)?
501            .into_iter()
502            .map(|definition| definition.to_link())
503            .collect::<Vec<_>>();
504        let mut substitution_links = self
505            .resolve_patterns_readonly(storage, substitutions, solution, true)?
506            .into_iter()
507            .map(|definition| definition.to_link())
508            .collect::<Vec<_>>();
509
510        restriction_links.sort_by_key(|link| link.index);
511        substitution_links.sort_by_key(|link| link.index);
512
513        Ok(restriction_links == substitution_links)
514    }
515
516    fn resolve_patterns_readonly(
517        &self,
518        storage: &mut impl NamedTypeLinks,
519        patterns: &[Pattern],
520        solution: &HashMap<String, u32>,
521        is_substitution: bool,
522    ) -> Result<Vec<ResolvedLink>> {
523        let mut resolved = Vec::new();
524        for pattern in patterns {
525            if let Some(link) =
526                self.resolve_pattern_readonly(storage, pattern, solution, is_substitution)?
527            {
528                resolved.push(link);
529            }
530        }
531        Ok(resolved)
532    }
533
534    fn resolve_pattern_readonly(
535        &self,
536        storage: &mut impl NamedTypeLinks,
537        pattern: &Pattern,
538        solution: &HashMap<String, u32>,
539        is_substitution: bool,
540    ) -> Result<Option<ResolvedLink>> {
541        if pattern.is_leaf() {
542            let index = self.resolve_identifier_readonly(
543                storage,
544                &pattern.index,
545                solution,
546                if is_substitution { 0 } else { u32::MAX },
547            )?;
548            return Ok(Some(ResolvedLink::new(index, u32::MAX, u32::MAX, None)));
549        }
550
551        let source_pattern = pattern
552            .source
553            .as_deref()
554            .ok_or_else(|| LinkError::InvalidFormat("Invalid source pattern".to_string()))?;
555        let target_pattern = pattern
556            .target
557            .as_deref()
558            .ok_or_else(|| LinkError::InvalidFormat("Invalid target pattern".to_string()))?;
559
560        let source = self
561            .resolve_pattern_readonly(storage, source_pattern, solution, is_substitution)?
562            .ok_or_else(|| LinkError::InvalidFormat("Invalid source pattern".to_string()))?
563            .index;
564        let target = self
565            .resolve_pattern_readonly(storage, target_pattern, solution, is_substitution)?
566            .ok_or_else(|| LinkError::InvalidFormat("Invalid target pattern".to_string()))?
567            .index;
568        let default_index = if is_substitution { 0 } else { u32::MAX };
569        let index =
570            self.resolve_identifier_readonly(storage, &pattern.index, solution, default_index)?;
571
572        Ok(Some(ResolvedLink::new(index, source, target, None)))
573    }
574
575    fn resolve_identifier_readonly(
576        &self,
577        storage: &mut impl NamedTypeLinks,
578        identifier: &str,
579        solution: &HashMap<String, u32>,
580        default_value: u32,
581    ) -> Result<u32> {
582        if identifier.is_empty() {
583            return Ok(default_value);
584        }
585        if identifier == "*" {
586            return Ok(u32::MAX);
587        }
588        if let Some(value) = solution.get(identifier) {
589            return Ok(*value);
590        }
591        if Self::is_variable(identifier) {
592            return Ok(default_value);
593        }
594        if let Ok(parsed) = identifier.parse::<u32>() {
595            return Ok(parsed);
596        }
597        Ok(storage.get_by_name(identifier)?.unwrap_or(default_value))
598    }
599
600    fn resolve_patterns(
601        &self,
602        storage: &mut impl NamedTypeLinks,
603        patterns: &[Pattern],
604        solution: &HashMap<String, u32>,
605        is_substitution: bool,
606    ) -> Result<Vec<ResolvedLink>> {
607        let mut working_solution = solution.clone();
608        let mut visited_indexes = HashSet::new();
609        let mut resolved = Vec::new();
610        for pattern in patterns {
611            resolved.push(self.resolve_pattern(
612                storage,
613                pattern,
614                &mut working_solution,
615                is_substitution,
616                &mut visited_indexes,
617            )?);
618        }
619        Ok(resolved)
620    }
621
622    fn resolve_pattern(
623        &self,
624        storage: &mut impl NamedTypeLinks,
625        pattern: &Pattern,
626        solution: &mut HashMap<String, u32>,
627        is_substitution: bool,
628        visited_indexes: &mut HashSet<u32>,
629    ) -> Result<ResolvedLink> {
630        if pattern.is_leaf() {
631            let index = self.resolve_identifier(
632                storage,
633                &pattern.index,
634                solution,
635                if is_substitution { 0 } else { u32::MAX },
636                is_substitution,
637            )?;
638            return Ok(ResolvedLink::new(index, u32::MAX, u32::MAX, None));
639        }
640
641        let mut source = self
642            .resolve_pattern(
643                storage,
644                pattern.source.as_deref().unwrap(),
645                solution,
646                is_substitution,
647                visited_indexes,
648            )?
649            .index;
650        let mut target = self
651            .resolve_pattern(
652                storage,
653                pattern.target.as_deref().unwrap(),
654                solution,
655                is_substitution,
656                visited_indexes,
657            )?
658            .index;
659        let default_index = if is_substitution { 0 } else { u32::MAX };
660        let mut index =
661            self.resolve_identifier(storage, &pattern.index, solution, default_index, false)?;
662        let mut name = None;
663
664        if is_substitution
665            && !pattern.index.is_empty()
666            && !Self::is_numeric_or_wildcard(&pattern.index)
667            && !Self::is_variable(&pattern.index)
668        {
669            name = Some(pattern.index.clone());
670            if index == 0 {
671                if let Some(existing_id) = storage.search(source, target) {
672                    index = existing_id;
673                }
674            }
675        }
676
677        if is_substitution {
678            Self::preserve_existing_substitution_parts(
679                storage,
680                pattern,
681                solution,
682                index,
683                &mut source,
684                &mut target,
685                visited_indexes,
686            )?;
687        }
688
689        Ok(ResolvedLink::new(index, source, target, name))
690    }
691
692    fn resolve_identifier(
693        &self,
694        storage: &mut impl NamedTypeLinks,
695        identifier: &str,
696        solution: &HashMap<String, u32>,
697        default_value: u32,
698        create_named_leaf: bool,
699    ) -> Result<u32> {
700        if identifier.is_empty() {
701            return Ok(default_value);
702        }
703        if identifier == "*" {
704            return Ok(u32::MAX);
705        }
706        if let Some(value) = solution.get(identifier) {
707            return Ok(*value);
708        }
709        if Self::is_variable(identifier) {
710            return Ok(default_value);
711        }
712        if let Ok(parsed) = identifier.parse::<u32>() {
713            return Ok(parsed);
714        }
715        if let Some(named_id) = storage.get_by_name(identifier)? {
716            return Ok(named_id);
717        }
718        if create_named_leaf {
719            return storage.get_or_create_named(identifier);
720        }
721        Ok(default_value)
722    }
723
724    fn determine_operations(
725        &self,
726        restrictions: &[ResolvedLink],
727        substitutions: &[ResolvedLink],
728    ) -> Vec<(Option<ResolvedLink>, Option<ResolvedLink>)> {
729        let mut operations = Vec::new();
730        let mut restriction_by_index = HashMap::new();
731        let mut substitution_by_index = HashMap::new();
732        let mut wildcard_restrictions = Vec::new();
733        let mut wildcard_substitutions = Vec::new();
734
735        for restriction in restrictions {
736            if Self::is_normal_index(restriction.index) {
737                restriction_by_index.insert(restriction.index, restriction.clone());
738            } else {
739                wildcard_restrictions.push(restriction.clone());
740            }
741        }
742
743        for substitution in substitutions {
744            if Self::is_normal_index(substitution.index) {
745                substitution_by_index.insert(substitution.index, substitution.clone());
746            } else {
747                wildcard_substitutions.push(substitution.clone());
748            }
749        }
750
751        let mut all_indices = restriction_by_index
752            .keys()
753            .chain(substitution_by_index.keys())
754            .copied()
755            .collect::<Vec<_>>();
756        all_indices.sort_unstable();
757        all_indices.dedup();
758
759        for index in all_indices {
760            match (
761                restriction_by_index.get(&index),
762                substitution_by_index.get(&index),
763            ) {
764                (Some(before), Some(after)) => {
765                    operations.push((Some(before.clone()), Some(after.clone())));
766                }
767                (Some(before), None) => operations.push((Some(before.clone()), None)),
768                (None, Some(after)) => operations.push((None, Some(after.clone()))),
769                (None, None) => {}
770            }
771        }
772
773        operations.extend(
774            wildcard_restrictions
775                .into_iter()
776                .map(|restriction| (Some(restriction), None)),
777        );
778        operations.extend(
779            wildcard_substitutions
780                .into_iter()
781                .map(|substitution| (None, Some(substitution))),
782        );
783
784        operations
785    }
786
787    fn apply_operation(
788        &self,
789        storage: &mut impl NamedTypeLinks,
790        before: Option<ResolvedLink>,
791        after: Option<ResolvedLink>,
792        changes: &mut Vec<(Option<Link>, Option<Link>)>,
793    ) -> Result<()> {
794        match (before, after) {
795            (Some(before), None) => {
796                let mut links = self.links_matching_definition(storage, &before)?;
797                links.sort_by_key(|link| link.index);
798                links.dedup_by_key(|link| link.index);
799                for link in links {
800                    if storage.exists(link.index) {
801                        let deleted = storage.delete(link.index)?;
802                        changes.push((Some(deleted), None));
803                    }
804                }
805            }
806            (None, Some(after)) => {
807                let created = self.create_or_update_resolved_link(storage, &after)?;
808                changes.push((None, Some(created)));
809            }
810            (Some(before), Some(after)) => {
811                if before.index == after.index && storage.exists(before.index) {
812                    let before_link = storage.get_link(before.index).unwrap();
813                    if before_link.source != after.source || before_link.target != after.target {
814                        storage.update(before.index, after.source, after.target)?;
815                    }
816                    if let Some(name) = &after.name {
817                        storage.set_name(before.index, name)?;
818                    }
819                    let after_link = storage.get_link(before.index).unwrap();
820                    changes.push((Some(before_link), Some(after_link)));
821                } else {
822                    self.apply_operation(storage, Some(before), None, changes)?;
823                    self.apply_operation(storage, None, Some(after), changes)?;
824                }
825            }
826            (None, None) => {}
827        }
828
829        Ok(())
830    }
831
832    fn create_or_update_resolved_link(
833        &self,
834        storage: &mut impl NamedTypeLinks,
835        definition: &ResolvedLink,
836    ) -> Result<Link> {
837        let id = if Self::is_normal_index(definition.index) {
838            storage.try_ensure_created(definition.index)?;
839            storage.update(definition.index, definition.source, definition.target)?;
840            definition.index
841        } else if let Some(existing_id) = storage.search(definition.source, definition.target) {
842            existing_id
843        } else {
844            storage.create(definition.source, definition.target)
845        };
846
847        if let Some(name) = &definition.name {
848            storage.set_name(id, name)?;
849        }
850
851        Ok(storage.get_link(id).unwrap())
852    }
853
854    fn links_matching_definition(
855        &self,
856        storage: &mut impl NamedTypeLinks,
857        definition: &ResolvedLink,
858    ) -> Result<Vec<Link>> {
859        Ok(storage
860            .all_links()
861            .into_iter()
862            .filter(|link| {
863                (definition.index == 0
864                    || Self::is_any(definition.index)
865                    || link.index == definition.index)
866                    && (Self::is_any(definition.source) || link.source == definition.source)
867                    && (Self::is_any(definition.target) || link.target == definition.target)
868            })
869            .collect())
870    }
871
872    fn assign_variable(id: &str, value: u32, assignments: &mut HashMap<String, u32>) {
873        if Self::is_variable(id) && value != 0 {
874            assignments.insert(id.to_string(), value);
875        }
876    }
877
878    fn is_variable(identifier: &str) -> bool {
879        !identifier.is_empty() && identifier.starts_with('$')
880    }
881
882    fn is_any(value: u32) -> bool {
883        value == u32::MAX
884    }
885
886    fn is_normal_index(value: u32) -> bool {
887        value != 0 && !Self::is_any(value)
888    }
889
890    fn is_numeric_or_wildcard(identifier: &str) -> bool {
891        identifier == "*" || identifier.parse::<u32>().is_ok()
892    }
893
894    /// Ensures a link is created from a LinoLink pattern
895    fn ensure_link_created(
896        &self,
897        storage: &mut impl NamedTypeLinks,
898        lino_link: &LinoLink,
899    ) -> Result<u32> {
900        // Handle leaf nodes (names or numbers)
901        if !lino_link.has_values() {
902            if let Some(ref id) = lino_link.id {
903                if id == "*" || Self::is_variable(id) {
904                    return Ok(u32::MAX);
905                }
906
907                // Check if it's a number
908                if let Ok(num) = id.parse::<u32>() {
909                    return Ok(num);
910                }
911
912                // It's a name - get or create
913                return storage.get_or_create_named(id);
914            }
915            return Ok(0);
916        }
917
918        // Handle composite links with 2 values
919        if lino_link.values_count() == 2 {
920            let values = lino_link.values.as_ref().unwrap();
921
922            // Recursively ensure source and target exist
923            let source_id = self.ensure_link_created(storage, &values[0])?;
924            let target_id = self.ensure_link_created(storage, &values[1])?;
925
926            // Create or get the composite link
927            let link_id = if let Some(ref id) = lino_link.id {
928                if let Ok(num) = id.parse::<u32>() {
929                    // Specific ID requested
930                    storage.try_ensure_created(num)?;
931                    storage.update(num, source_id, target_id)?;
932                    num
933                } else if id == "*" || Self::is_variable(id) {
934                    storage.get_or_create(source_id, target_id)
935                } else {
936                    // Named link
937                    let existing = storage.get_by_name(id)?;
938                    if let Some(id_num) = existing {
939                        storage.update(id_num, source_id, target_id)?;
940                        id_num
941                    } else {
942                        let new_id = storage.create(source_id, target_id);
943                        storage.set_name(new_id, id)?;
944                        new_id
945                    }
946                }
947            } else {
948                // Anonymous link
949                storage.get_or_create(source_id, target_id)
950            };
951
952            return Ok(link_id);
953        }
954
955        Err(LinkError::InvalidFormat("Invalid link structure".to_string()).into())
956    }
957
958    /// Simplifies the changes list
959    fn simplify_changes_list(
960        &self,
961        changes: &[(Option<Link>, Option<Link>)],
962    ) -> Vec<(Option<Link>, Option<Link>)> {
963        // Convert to the format expected by simplify_changes
964        let mut to_simplify: Vec<(Link, Link)> = Vec::new();
965        let mut non_simplifiable: Vec<(Option<Link>, Option<Link>)> = Vec::new();
966
967        for (before, after) in changes {
968            match (before, after) {
969                (Some(b), Some(a)) => {
970                    to_simplify.push((*b, *a));
971                }
972                _ => {
973                    non_simplifiable.push((*before, *after));
974                }
975            }
976        }
977
978        let simplified = simplify_changes(to_simplify);
979
980        let mut result: Vec<(Option<Link>, Option<Link>)> = non_simplifiable;
981        for (b, a) in simplified {
982            result.push((Some(b), Some(a)));
983        }
984
985        result
986    }
987
988    /// Logs a trace message if tracing is enabled
989    fn trace_msg(&self, msg: &str) {
990        if self.trace {
991            eprintln!("{}", msg);
992        }
993    }
994}