1use crate::error::{LearningError, Result};
3use crate::models::{Rule, RuleScope};
4use std::collections::HashMap;
5
6pub struct ConflictResolver;
8
9impl ConflictResolver {
10 pub fn new() -> Self {
12 Self
13 }
14
15 pub fn detect_conflict(rule1: &Rule, rule2: &Rule) -> bool {
17 rule1.pattern == rule2.pattern && rule1.action != rule2.action
19 }
20
21 pub fn find_conflicts(rules: &[Rule]) -> Vec<(Rule, Rule)> {
23 let mut conflicts = Vec::new();
24
25 for i in 0..rules.len() {
26 for j in (i + 1)..rules.len() {
27 if Self::detect_conflict(&rules[i], &rules[j]) {
28 conflicts.push((rules[i].clone(), rules[j].clone()));
29 }
30 }
31 }
32
33 conflicts
34 }
35
36 pub fn check_conflicts(rule: &Rule, existing_rules: &[Rule]) -> Result<()> {
38 for existing_rule in existing_rules {
39 if Self::detect_conflict(rule, existing_rule) {
40 return Err(LearningError::ConflictResolutionFailed(
41 format!(
42 "Rule '{}' conflicts with existing rule '{}': both match pattern '{}' but have different actions",
43 rule.id, existing_rule.id, rule.pattern
44 ),
45 ));
46 }
47 }
48 Ok(())
49 }
50
51 pub fn apply_precedence(rules: &[Rule]) -> Option<Rule> {
54 if rules.is_empty() {
55 return None;
56 }
57
58 let mut sorted_rules = rules.to_vec();
60 sorted_rules.sort_by_key(|r| match r.scope {
61 RuleScope::Project => 0,
62 RuleScope::Global => 1,
63 RuleScope::Session => 2,
64 });
65
66 Some(sorted_rules[0].clone())
67 }
68
69 pub fn get_rules_by_pattern_with_precedence(
71 rules: &[Rule],
72 pattern: &str,
73 ) -> Vec<Rule> {
74 let matching_rules: Vec<Rule> = rules
75 .iter()
76 .filter(|r| r.pattern == pattern)
77 .cloned()
78 .collect();
79
80 if matching_rules.is_empty() {
81 return Vec::new();
82 }
83
84 let mut result = Vec::new();
86 let mut seen_patterns = std::collections::HashSet::new();
87
88 for rule in &matching_rules {
89 if !seen_patterns.contains(&rule.pattern) {
90 if let Some(precedent_rule) = Self::apply_precedence(
91 &matching_rules
92 .iter()
93 .filter(|r| r.pattern == rule.pattern)
94 .cloned()
95 .collect::<Vec<_>>(),
96 ) {
97 result.push(precedent_rule);
98 seen_patterns.insert(rule.pattern.clone());
99 }
100 }
101 }
102
103 result
104 }
105
106 pub fn resolve_conflicts(rules: &[Rule]) -> Result<Vec<Rule>> {
108 let mut pattern_groups: HashMap<String, Vec<Rule>> = HashMap::new();
110
111 for rule in rules {
112 pattern_groups
113 .entry(rule.pattern.clone())
114 .or_insert_with(Vec::new)
115 .push(rule.clone());
116 }
117
118 let mut resolved_rules = Vec::new();
120
121 for (_, group) in pattern_groups {
122 if let Some(rule) = Self::apply_precedence(&group) {
123 resolved_rules.push(rule);
124 }
125 }
126
127 Ok(resolved_rules)
128 }
129
130 pub fn log_conflict_resolution(
132 selected_rule: &Rule,
133 conflicting_rules: &[Rule],
134 ) -> String {
135 let conflicting_ids: Vec<String> = conflicting_rules
136 .iter()
137 .map(|r| r.id.clone())
138 .collect();
139
140 format!(
141 "Conflict resolution: Selected rule '{}' (scope: {}) over conflicting rules: {}",
142 selected_rule.id,
143 selected_rule.scope,
144 conflicting_ids.join(", ")
145 )
146 }
147
148 pub fn get_highest_priority_rule(rules: &[Rule], pattern: &str) -> Option<Rule> {
150 let matching_rules: Vec<Rule> = rules
151 .iter()
152 .filter(|r| r.pattern == pattern)
153 .cloned()
154 .collect();
155
156 Self::apply_precedence(&matching_rules)
157 }
158
159 pub fn check_cross_scope_conflicts(
161 project_rules: &[Rule],
162 global_rules: &[Rule],
163 ) -> Vec<(Rule, Rule)> {
164 let mut conflicts = Vec::new();
165
166 for project_rule in project_rules {
167 for global_rule in global_rules {
168 if Self::detect_conflict(project_rule, global_rule) {
169 conflicts.push((project_rule.clone(), global_rule.clone()));
170 }
171 }
172 }
173
174 conflicts
175 }
176}
177
178impl Default for ConflictResolver {
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::models::RuleSource;
188
189 fn create_test_rule(
190 id: &str,
191 scope: RuleScope,
192 pattern: &str,
193 action: &str,
194 ) -> Rule {
195 let mut rule = Rule::new(
196 scope,
197 pattern.to_string(),
198 action.to_string(),
199 RuleSource::Learned,
200 );
201 rule.id = id.to_string();
202 rule
203 }
204
205 #[test]
206 fn test_detect_conflict_same_pattern_different_action() {
207 let rule1 = create_test_rule("rule1", RuleScope::Global, "pattern1", "action1");
208 let rule2 = create_test_rule("rule2", RuleScope::Global, "pattern1", "action2");
209
210 assert!(ConflictResolver::detect_conflict(&rule1, &rule2));
211 }
212
213 #[test]
214 fn test_detect_conflict_same_pattern_same_action() {
215 let rule1 = create_test_rule("rule1", RuleScope::Global, "pattern1", "action1");
216 let rule2 = create_test_rule("rule2", RuleScope::Global, "pattern1", "action1");
217
218 assert!(!ConflictResolver::detect_conflict(&rule1, &rule2));
219 }
220
221 #[test]
222 fn test_detect_conflict_different_pattern() {
223 let rule1 = create_test_rule("rule1", RuleScope::Global, "pattern1", "action1");
224 let rule2 = create_test_rule("rule2", RuleScope::Global, "pattern2", "action1");
225
226 assert!(!ConflictResolver::detect_conflict(&rule1, &rule2));
227 }
228
229 #[test]
230 fn test_find_conflicts() {
231 let rules = vec![
232 create_test_rule("rule1", RuleScope::Global, "pattern1", "action1"),
233 create_test_rule("rule2", RuleScope::Global, "pattern1", "action2"),
234 create_test_rule("rule3", RuleScope::Global, "pattern2", "action1"),
235 ];
236
237 let conflicts = ConflictResolver::find_conflicts(&rules);
238 assert_eq!(conflicts.len(), 1);
239 assert_eq!(conflicts[0].0.id, "rule1");
240 assert_eq!(conflicts[0].1.id, "rule2");
241 }
242
243 #[test]
244 fn test_check_conflicts_no_conflict() {
245 let rule = create_test_rule("rule1", RuleScope::Global, "pattern1", "action1");
246 let existing_rules = vec![
247 create_test_rule("rule2", RuleScope::Global, "pattern2", "action1"),
248 create_test_rule("rule3", RuleScope::Global, "pattern3", "action2"),
249 ];
250
251 let result = ConflictResolver::check_conflicts(&rule, &existing_rules);
252 assert!(result.is_ok());
253 }
254
255 #[test]
256 fn test_check_conflicts_with_conflict() {
257 let rule = create_test_rule("rule1", RuleScope::Global, "pattern1", "action1");
258 let existing_rules = vec![
259 create_test_rule("rule2", RuleScope::Global, "pattern1", "action2"),
260 ];
261
262 let result = ConflictResolver::check_conflicts(&rule, &existing_rules);
263 assert!(result.is_err());
264 }
265
266 #[test]
267 fn test_apply_precedence_project_over_global() {
268 let rules = vec![
269 create_test_rule("rule1", RuleScope::Global, "pattern1", "action1"),
270 create_test_rule("rule2", RuleScope::Project, "pattern1", "action2"),
271 ];
272
273 let selected = ConflictResolver::apply_precedence(&rules);
274 assert!(selected.is_some());
275 assert_eq!(selected.unwrap().id, "rule2");
276 }
277
278 #[test]
279 fn test_apply_precedence_global_over_session() {
280 let rules = vec![
281 create_test_rule("rule1", RuleScope::Session, "pattern1", "action1"),
282 create_test_rule("rule2", RuleScope::Global, "pattern1", "action2"),
283 ];
284
285 let selected = ConflictResolver::apply_precedence(&rules);
286 assert!(selected.is_some());
287 assert_eq!(selected.unwrap().id, "rule2");
288 }
289
290 #[test]
291 fn test_apply_precedence_project_over_all() {
292 let rules = vec![
293 create_test_rule("rule1", RuleScope::Session, "pattern1", "action1"),
294 create_test_rule("rule2", RuleScope::Global, "pattern1", "action2"),
295 create_test_rule("rule3", RuleScope::Project, "pattern1", "action3"),
296 ];
297
298 let selected = ConflictResolver::apply_precedence(&rules);
299 assert!(selected.is_some());
300 assert_eq!(selected.unwrap().id, "rule3");
301 }
302
303 #[test]
304 fn test_apply_precedence_empty() {
305 let rules = vec![];
306 let selected = ConflictResolver::apply_precedence(&rules);
307 assert!(selected.is_none());
308 }
309
310 #[test]
311 fn test_resolve_conflicts_multiple_patterns() {
312 let rules = vec![
313 create_test_rule("rule1", RuleScope::Global, "pattern1", "action1"),
314 create_test_rule("rule2", RuleScope::Project, "pattern1", "action2"),
315 create_test_rule("rule3", RuleScope::Global, "pattern2", "action1"),
316 create_test_rule("rule4", RuleScope::Session, "pattern2", "action2"),
317 ];
318
319 let resolved = ConflictResolver::resolve_conflicts(&rules).unwrap();
320 assert_eq!(resolved.len(), 2);
321
322 let pattern1_rule = resolved.iter().find(|r| r.pattern == "pattern1").unwrap();
324 assert_eq!(pattern1_rule.id, "rule2");
325
326 let pattern2_rule = resolved.iter().find(|r| r.pattern == "pattern2").unwrap();
328 assert_eq!(pattern2_rule.id, "rule3");
329 }
330
331 #[test]
332 fn test_log_conflict_resolution() {
333 let selected = create_test_rule("rule1", RuleScope::Project, "pattern1", "action1");
334 let conflicting = vec![
335 create_test_rule("rule2", RuleScope::Global, "pattern1", "action2"),
336 ];
337
338 let log = ConflictResolver::log_conflict_resolution(&selected, &conflicting);
339 assert!(log.contains("rule1"));
340 assert!(log.contains("project"));
341 assert!(log.contains("rule2"));
342 }
343
344 #[test]
345 fn test_get_highest_priority_rule() {
346 let rules = vec![
347 create_test_rule("rule1", RuleScope::Global, "pattern1", "action1"),
348 create_test_rule("rule2", RuleScope::Project, "pattern1", "action2"),
349 create_test_rule("rule3", RuleScope::Session, "pattern1", "action3"),
350 ];
351
352 let highest = ConflictResolver::get_highest_priority_rule(&rules, "pattern1");
353 assert!(highest.is_some());
354 assert_eq!(highest.unwrap().id, "rule2");
355 }
356
357 #[test]
358 fn test_get_highest_priority_rule_not_found() {
359 let rules = vec![
360 create_test_rule("rule1", RuleScope::Global, "pattern1", "action1"),
361 ];
362
363 let highest = ConflictResolver::get_highest_priority_rule(&rules, "pattern2");
364 assert!(highest.is_none());
365 }
366
367 #[test]
368 fn test_check_cross_scope_conflicts() {
369 let project_rules = vec![
370 create_test_rule("rule1", RuleScope::Project, "pattern1", "action1"),
371 ];
372
373 let global_rules = vec![
374 create_test_rule("rule2", RuleScope::Global, "pattern1", "action2"),
375 ];
376
377 let conflicts = ConflictResolver::check_cross_scope_conflicts(&project_rules, &global_rules);
378 assert_eq!(conflicts.len(), 1);
379 }
380
381 #[test]
382 fn test_check_cross_scope_no_conflicts() {
383 let project_rules = vec![
384 create_test_rule("rule1", RuleScope::Project, "pattern1", "action1"),
385 ];
386
387 let global_rules = vec![
388 create_test_rule("rule2", RuleScope::Global, "pattern2", "action1"),
389 ];
390
391 let conflicts = ConflictResolver::check_cross_scope_conflicts(&project_rules, &global_rules);
392 assert_eq!(conflicts.len(), 0);
393 }
394
395 #[test]
396 fn test_get_rules_by_pattern_with_precedence() {
397 let rules = vec![
398 create_test_rule("rule1", RuleScope::Global, "pattern1", "action1"),
399 create_test_rule("rule2", RuleScope::Project, "pattern1", "action2"),
400 create_test_rule("rule3", RuleScope::Session, "pattern1", "action3"),
401 create_test_rule("rule4", RuleScope::Global, "pattern2", "action1"),
402 ];
403
404 let pattern1_rules = ConflictResolver::get_rules_by_pattern_with_precedence(&rules, "pattern1");
405 assert_eq!(pattern1_rules.len(), 1);
406 assert_eq!(pattern1_rules[0].id, "rule2");
407
408 let pattern2_rules = ConflictResolver::get_rules_by_pattern_with_precedence(&rules, "pattern2");
409 assert_eq!(pattern2_rules.len(), 1);
410 assert_eq!(pattern2_rules[0].id, "rule4");
411 }
412}