rust_rule_engine/engine/
module.rs

1//! Module System (CLIPS-inspired defmodule)
2//!
3//! Provides namespace isolation and visibility control for large knowledge bases.
4//! Similar to CLIPS defmodule, Drools rule units, and package systems.
5//!
6//! # Features
7//!
8//! - **Module Isolation**: Separate namespaces for rules, templates, and facts
9//! - **Import/Export Control**: Fine-grained visibility management
10//! - **Module Focus**: Control execution flow across modules
11//! - **Pattern Matching**: Export/import with wildcards (e.g., "sensor-*")
12//!
13//! # Example
14//!
15//! ```rust
16//! use rust_rule_engine::engine::module::{ModuleManager, ExportList, ImportType};
17//!
18//! let mut manager = ModuleManager::new();
19//!
20//! // Create modules
21//! manager.create_module("SENSORS").unwrap();
22//! manager.create_module("CONTROL").unwrap();
23//!
24//! // Configure exports
25//! manager.export_all_from("SENSORS", ExportList::All).unwrap();
26//!
27//! // Configure imports
28//! manager.import_from("CONTROL", "SENSORS", ImportType::AllTemplates, "*").unwrap();
29//!
30//! // Set focus
31//! manager.set_focus("CONTROL").unwrap();
32//! ```
33
34use crate::errors::{Result, RuleEngineError};
35use std::collections::{HashMap, HashSet, VecDeque};
36
37/// Type of item that can be exported/imported
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub enum ItemType {
40    /// Rule
41    Rule,
42    /// Template (deftemplate)
43    Template,
44    /// Fact
45    Fact,
46    /// All items
47    All,
48}
49
50/// Export list specification
51#[derive(Debug, Clone, PartialEq)]
52pub enum ExportList {
53    /// Export everything (default for MAIN module)
54    All,
55    /// Export nothing (default for user modules)
56    None,
57    /// Export specific items matching patterns
58    Specific(Vec<ExportItem>),
59}
60
61/// Single export item
62#[derive(Debug, Clone, PartialEq)]
63pub struct ExportItem {
64    /// Type of item to export
65    pub item_type: ItemType,
66    /// Name or pattern (supports wildcards like "sensor-*")
67    pub pattern: String,
68}
69
70/// Import type specification
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum ImportType {
73    /// Import all rules
74    AllRules,
75    /// Import all templates
76    AllTemplates,
77    /// Import specific rules
78    Rules,
79    /// Import specific templates
80    Templates,
81    /// Import everything
82    All,
83}
84
85/// Re-export configuration
86#[derive(Debug, Clone, PartialEq)]
87pub struct ReExport {
88    /// Items to re-export (patterns from imported modules)
89    pub patterns: Vec<String>,
90    /// Whether to re-export transitively (re-export what was imported from other modules)
91    pub transitive: bool,
92}
93
94/// Import declaration
95#[derive(Debug, Clone, PartialEq)]
96pub struct ImportDecl {
97    /// Module to import from
98    pub from_module: String,
99    /// Type of items to import
100    pub import_type: ImportType,
101    /// Pattern to match (supports wildcards)
102    pub pattern: String,
103    /// Re-export configuration (if any)
104    pub re_export: Option<ReExport>,
105}
106
107/// A module in the knowledge base
108#[derive(Debug, Clone)]
109pub struct Module {
110    /// Module name
111    pub name: String,
112    /// Rules owned by this module
113    rules: HashSet<String>,
114    /// Templates owned by this module
115    templates: HashSet<String>,
116    /// Facts owned by this module (by type)
117    fact_types: HashSet<String>,
118    /// Export specification
119    exports: ExportList,
120    /// Import declarations
121    imports: Vec<ImportDecl>,
122    /// Module documentation
123    pub doc: Option<String>,
124    /// Module-level salience (priority adjustment for all rules in this module)
125    salience: i32,
126}
127
128impl Module {
129    /// Create a new module
130    pub fn new(name: impl Into<String>) -> Self {
131        let name = name.into();
132        let exports = if name == "MAIN" {
133            ExportList::All
134        } else {
135            ExportList::None
136        };
137
138        Self {
139            name,
140            rules: HashSet::new(),
141            templates: HashSet::new(),
142            fact_types: HashSet::new(),
143            exports,
144            imports: Vec::new(),
145            doc: None,
146            salience: 0,
147        }
148    }
149
150    /// Add documentation
151    pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
152        self.doc = Some(doc.into());
153        self
154    }
155
156    /// Add a rule to this module
157    pub fn add_rule(&mut self, rule_name: impl Into<String>) {
158        self.rules.insert(rule_name.into());
159    }
160
161    /// Add a template to this module
162    pub fn add_template(&mut self, template_name: impl Into<String>) {
163        self.templates.insert(template_name.into());
164    }
165
166    /// Add a fact type to this module
167    pub fn add_fact_type(&mut self, fact_type: impl Into<String>) {
168        self.fact_types.insert(fact_type.into());
169    }
170
171    /// Set export specification
172    pub fn set_exports(&mut self, exports: ExportList) {
173        self.exports = exports;
174    }
175
176    /// Get export specification
177    pub fn get_exports(&self) -> &ExportList {
178        &self.exports
179    }
180
181    /// Add an import declaration
182    pub fn add_import(&mut self, import: ImportDecl) {
183        self.imports.push(import);
184    }
185
186    /// Check if this module exports a rule (including re-exports)
187    pub fn exports_rule(&self, rule_name: &str) -> bool {
188        // Check if it's an owned rule
189        let is_owned = self.rules.contains(rule_name);
190
191        // Check if it matches export spec for owned rules
192        let exports_owned = match &self.exports {
193            ExportList::All => is_owned,
194            ExportList::None => false,
195            ExportList::Specific(items) => {
196                is_owned
197                    && items.iter().any(|item| {
198                        matches!(item.item_type, ItemType::Rule | ItemType::All)
199                            && pattern_matches(&item.pattern, rule_name)
200                    })
201            }
202        };
203
204        // Also check if it should be re-exported (transitive)
205        exports_owned || self.should_re_export_rule(rule_name)
206    }
207
208    /// Check if this module exports a template (including re-exports)
209    pub fn exports_template(&self, template_name: &str) -> bool {
210        // Check if it's an owned template
211        let is_owned = self.templates.contains(template_name);
212
213        // Check if it matches export spec for owned templates
214        let exports_owned = match &self.exports {
215            ExportList::All => is_owned,
216            ExportList::None => false,
217            ExportList::Specific(items) => {
218                is_owned
219                    && items.iter().any(|item| {
220                        matches!(item.item_type, ItemType::Template | ItemType::All)
221                            && pattern_matches(&item.pattern, template_name)
222                    })
223            }
224        };
225
226        // Also check if it should be re-exported (transitive)
227        exports_owned || self.should_re_export_template(template_name)
228    }
229
230    /// Get all rules in this module
231    pub fn get_rules(&self) -> &HashSet<String> {
232        &self.rules
233    }
234
235    /// Get all templates in this module
236    pub fn get_templates(&self) -> &HashSet<String> {
237        &self.templates
238    }
239
240    /// Get all imports
241    pub fn get_imports(&self) -> &[ImportDecl] {
242        &self.imports
243    }
244
245    /// Set module-level salience (priority adjustment for all rules)
246    pub fn set_salience(&mut self, salience: i32) {
247        self.salience = salience;
248    }
249
250    /// Get module-level salience
251    pub fn get_salience(&self) -> i32 {
252        self.salience
253    }
254
255    /// Check if a rule should be re-exported based on import declarations
256    pub fn should_re_export_rule(&self, rule_name: &str) -> bool {
257        for import in &self.imports {
258            if let Some(re_export) = &import.re_export {
259                if re_export
260                    .patterns
261                    .iter()
262                    .any(|p| pattern_matches(p, rule_name))
263                {
264                    return true;
265                }
266            }
267        }
268        false
269    }
270
271    /// Check if a template should be re-exported based on import declarations
272    pub fn should_re_export_template(&self, template_name: &str) -> bool {
273        for import in &self.imports {
274            if let Some(re_export) = &import.re_export {
275                if re_export
276                    .patterns
277                    .iter()
278                    .any(|p| pattern_matches(p, template_name))
279                {
280                    return true;
281                }
282            }
283        }
284        false
285    }
286}
287
288/// Error type for cyclic import detection
289#[derive(Debug, Clone)]
290pub struct CycleError {
291    /// The cycle that was detected (list of module names forming the cycle)
292    pub cycle_path: Vec<String>,
293}
294
295impl std::fmt::Display for CycleError {
296    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297        write!(
298            f,
299            "Cyclic import detected: {}",
300            self.cycle_path.join(" -> ")
301        )
302    }
303}
304
305/// Module manager for organizing knowledge bases
306#[derive(Debug, Clone)]
307pub struct ModuleManager {
308    /// All modules
309    modules: HashMap<String, Module>,
310    /// Current focus module (for execution)
311    current_focus: String,
312    /// Default module name
313    default_module: String,
314    /// Track import graph for cycle detection
315    import_graph: HashMap<String, HashSet<String>>,
316}
317
318impl ModuleManager {
319    /// Create a new module manager
320    pub fn new() -> Self {
321        let mut modules = HashMap::new();
322        modules.insert("MAIN".to_string(), Module::new("MAIN"));
323
324        Self {
325            modules,
326            current_focus: "MAIN".to_string(),
327            default_module: "MAIN".to_string(),
328            import_graph: HashMap::new(),
329        }
330    }
331
332    /// Create a new module
333    pub fn create_module(&mut self, name: impl Into<String>) -> Result<&mut Module> {
334        let name = name.into();
335
336        if self.modules.contains_key(&name) {
337            return Err(RuleEngineError::ModuleError {
338                message: format!("Module '{}' already exists", name),
339            });
340        }
341
342        self.modules.insert(name.clone(), Module::new(&name));
343        Ok(self.modules.get_mut(&name).unwrap())
344    }
345
346    /// Get a module (mutable)
347    pub fn get_module_mut(&mut self, name: &str) -> Result<&mut Module> {
348        self.modules
349            .get_mut(name)
350            .ok_or_else(|| RuleEngineError::ModuleError {
351                message: format!("Module '{}' not found", name),
352            })
353    }
354
355    /// Get a module (immutable)
356    pub fn get_module(&self, name: &str) -> Result<&Module> {
357        self.modules
358            .get(name)
359            .ok_or_else(|| RuleEngineError::ModuleError {
360                message: format!("Module '{}' not found", name),
361            })
362    }
363
364    /// Delete a module
365    pub fn delete_module(&mut self, name: &str) -> Result<()> {
366        if name == self.default_module {
367            return Err(RuleEngineError::ModuleError {
368                message: "Cannot delete default module".to_string(),
369            });
370        }
371
372        if name == self.current_focus {
373            self.current_focus = self.default_module.clone();
374        }
375
376        self.modules
377            .remove(name)
378            .ok_or_else(|| RuleEngineError::ModuleError {
379                message: format!("Module '{}' not found", name),
380            })?;
381
382        // Clean up import graph
383        self.import_graph.remove(name);
384        for (_, imports) in self.import_graph.iter_mut() {
385            imports.remove(name);
386        }
387
388        Ok(())
389    }
390
391    /// Set current focus module
392    pub fn set_focus(&mut self, module_name: impl Into<String>) -> Result<()> {
393        let module_name = module_name.into();
394
395        if !self.modules.contains_key(&module_name) {
396            return Err(RuleEngineError::ModuleError {
397                message: format!("Module '{}' not found", module_name),
398            });
399        }
400
401        self.current_focus = module_name;
402        Ok(())
403    }
404
405    /// Get current focus module name
406    pub fn get_focus(&self) -> &str {
407        &self.current_focus
408    }
409
410    /// Get all module names
411    pub fn list_modules(&self) -> Vec<String> {
412        self.modules.keys().cloned().collect()
413    }
414
415    /// Configure exports for a module
416    pub fn export_all_from(&mut self, module_name: &str, export_list: ExportList) -> Result<()> {
417        let module = self.get_module_mut(module_name)?;
418        module.set_exports(export_list);
419        Ok(())
420    }
421
422    /// Detect if adding an import would create a cycle
423    ///
424    /// Uses BFS (Breadth-First Search) to traverse the import graph from `from_module`
425    /// and check if we can reach `to_module`. If we can, adding `to_module -> from_module`
426    /// would create a cycle.
427    ///
428    /// Returns `Ok(())` if no cycle would be created.
429    /// Returns `Err(RuleEngineError)` with detailed cycle path if cycle would be created.
430    fn detect_cycle(&self, to_module: &str, from_module: &str) -> Result<()> {
431        // Cycle with self
432        if to_module == from_module {
433            return Err(RuleEngineError::ModuleError {
434                message: format!(
435                    "Cyclic import detected: {} cannot import from itself",
436                    to_module
437                ),
438            });
439        }
440
441        // BFS from from_module to see if we can reach to_module
442        // If we can, then adding to_module -> from_module creates a cycle
443        let mut queue = VecDeque::new();
444        let mut visited = HashSet::new();
445        let mut parent_map: HashMap<String, String> = HashMap::new();
446
447        queue.push_back(from_module.to_string());
448        visited.insert(from_module.to_string());
449
450        while let Some(current) = queue.pop_front() {
451            // Get all modules that current imports from
452            if let Some(imports) = self.import_graph.get(&current) {
453                for imported in imports {
454                    if imported == to_module {
455                        // Found a cycle! Reconstruct the path
456                        let mut cycle_path = vec![to_module.to_string()];
457                        let mut node = current.clone();
458
459                        while let Some(parent) = parent_map.get(&node) {
460                            cycle_path.push(node.clone());
461                            node = parent.clone();
462                        }
463
464                        cycle_path.push(node);
465                        cycle_path.reverse();
466
467                        return Err(RuleEngineError::ModuleError {
468                            message: format!("Cyclic import detected: {}", cycle_path.join(" -> ")),
469                        });
470                    }
471
472                    if !visited.contains(imported) {
473                        visited.insert(imported.clone());
474                        parent_map.insert(imported.clone(), current.clone());
475                        queue.push_back(imported.clone());
476                    }
477                }
478            }
479        }
480
481        Ok(())
482    }
483
484    /// Get the import graph for inspection/debugging
485    pub fn get_import_graph(&self) -> &HashMap<String, HashSet<String>> {
486        &self.import_graph
487    }
488
489    /// Export the import graph for visualization or analysis
490    pub fn get_import_graph_debug(&self) -> Vec<(String, Vec<String>)> {
491        self.import_graph
492            .iter()
493            .map(|(module, imports)| (module.clone(), imports.iter().cloned().collect()))
494            .collect()
495    }
496
497    /// Add an import to a module
498    pub fn import_from(
499        &mut self,
500        to_module: &str,
501        from_module: &str,
502        import_type: ImportType,
503        pattern: impl Into<String>,
504    ) -> Result<()> {
505        self.import_from_with_reexport(to_module, from_module, import_type, pattern, None)
506    }
507
508    /// Add an import to a module with optional re-export configuration
509    pub fn import_from_with_reexport(
510        &mut self,
511        to_module: &str,
512        from_module: &str,
513        import_type: ImportType,
514        pattern: impl Into<String>,
515        re_export: Option<ReExport>,
516    ) -> Result<()> {
517        // Validate from_module exists
518        if !self.modules.contains_key(from_module) {
519            return Err(RuleEngineError::ModuleError {
520                message: format!("Source module '{}' not found", from_module),
521            });
522        }
523
524        // Check for cycles BEFORE adding the import
525        self.detect_cycle(to_module, from_module)?;
526
527        let module = self.get_module_mut(to_module)?;
528        module.add_import(ImportDecl {
529            from_module: from_module.to_string(),
530            import_type,
531            pattern: pattern.into(),
532            re_export,
533        });
534
535        // Update import graph
536        self.import_graph
537            .entry(to_module.to_string())
538            .or_default()
539            .insert(from_module.to_string());
540
541        Ok(())
542    }
543
544    /// Check if a rule is visible to a module
545    pub fn is_rule_visible(&self, rule_name: &str, to_module: &str) -> Result<bool> {
546        let module = self.get_module(to_module)?;
547
548        // Own rules are always visible
549        if module.get_rules().contains(rule_name) {
550            return Ok(true);
551        }
552
553        // Check imports
554        for import in module.get_imports() {
555            if !matches!(
556                import.import_type,
557                ImportType::AllRules | ImportType::Rules | ImportType::All
558            ) {
559                continue;
560            }
561
562            let from_module = self.get_module(&import.from_module)?;
563
564            if from_module.exports_rule(rule_name) && pattern_matches(&import.pattern, rule_name) {
565                return Ok(true);
566            }
567        }
568
569        Ok(false)
570    }
571
572    /// Check if a template is visible to a module
573    pub fn is_template_visible(&self, template_name: &str, to_module: &str) -> Result<bool> {
574        let module = self.get_module(to_module)?;
575
576        // Own templates are always visible
577        if module.get_templates().contains(template_name) {
578            return Ok(true);
579        }
580
581        // Check imports
582        for import in module.get_imports() {
583            if !matches!(
584                import.import_type,
585                ImportType::AllTemplates | ImportType::Templates | ImportType::All
586            ) {
587                continue;
588            }
589
590            let from_module = self.get_module(&import.from_module)?;
591
592            if from_module.exports_template(template_name)
593                && pattern_matches(&import.pattern, template_name)
594            {
595                return Ok(true);
596            }
597        }
598
599        Ok(false)
600    }
601
602    /// Get all rules visible to a module
603    pub fn get_visible_rules(&self, module_name: &str) -> Result<Vec<String>> {
604        let module = self.get_module(module_name)?;
605        let mut visible = HashSet::new();
606
607        // Add own rules
608        visible.extend(module.get_rules().iter().cloned());
609
610        // Add imported rules
611        for import in module.get_imports() {
612            if !matches!(
613                import.import_type,
614                ImportType::AllRules | ImportType::Rules | ImportType::All
615            ) {
616                continue;
617            }
618
619            let from_module = self.get_module(&import.from_module)?;
620
621            for rule in from_module.get_rules() {
622                if from_module.exports_rule(rule) && pattern_matches(&import.pattern, rule) {
623                    visible.insert(rule.clone());
624                }
625            }
626        }
627
628        Ok(visible.into_iter().collect())
629    }
630
631    /// Get module statistics
632    pub fn get_stats(&self) -> ModuleStats {
633        ModuleStats {
634            total_modules: self.modules.len(),
635            current_focus: self.current_focus.clone(),
636            modules: self
637                .modules
638                .iter()
639                .map(|(name, module)| {
640                    (
641                        name.clone(),
642                        ModuleInfo {
643                            name: name.clone(),
644                            rules_count: module.rules.len(),
645                            templates_count: module.templates.len(),
646                            imports_count: module.imports.len(),
647                            exports_type: match &module.exports {
648                                ExportList::All => "All".to_string(),
649                                ExportList::None => "None".to_string(),
650                                ExportList::Specific(items) => format!("Specific({})", items.len()),
651                            },
652                            salience: module.salience,
653                        },
654                    )
655                })
656                .collect(),
657        }
658    }
659
660    /// Set module-level salience
661    pub fn set_module_salience(&mut self, module_name: &str, salience: i32) -> Result<()> {
662        let module = self.get_module_mut(module_name)?;
663        module.set_salience(salience);
664        Ok(())
665    }
666
667    /// Get module-level salience
668    pub fn get_module_salience(&self, module_name: &str) -> Result<i32> {
669        let module = self.get_module(module_name)?;
670        Ok(module.get_salience())
671    }
672
673    /// Get all transitive dependencies of a module (BFS)
674    pub fn get_transitive_dependencies(&self, module_name: &str) -> Result<Vec<String>> {
675        let mut visited = HashSet::new();
676        let mut queue = VecDeque::new();
677        let mut result = Vec::new();
678
679        queue.push_back(module_name.to_string());
680        visited.insert(module_name.to_string());
681
682        while let Some(current) = queue.pop_front() {
683            if let Some(imports) = self.import_graph.get(&current) {
684                for imported in imports {
685                    if !visited.contains(imported) {
686                        visited.insert(imported.clone());
687                        result.push(imported.clone());
688                        queue.push_back(imported.clone());
689                    }
690                }
691            }
692        }
693
694        Ok(result)
695    }
696
697    /// Validate module configuration
698    pub fn validate_module(&self, module_name: &str) -> Result<ModuleValidation> {
699        let module = self.get_module(module_name)?;
700        let mut warnings = Vec::new();
701        let mut errors = Vec::new();
702
703        // Check for broken imports (imported modules don't exist)
704        for import in module.get_imports() {
705            if !self.modules.contains_key(&import.from_module) {
706                errors.push(format!(
707                    "Import references non-existent module: {}",
708                    import.from_module
709                ));
710            }
711        }
712
713        // Check for unused imports (imports nothing visible)
714        for import in module.get_imports() {
715            if let Ok(from_module) = self.get_module(&import.from_module) {
716                let mut has_visible = false;
717
718                // Check if any rules/templates are visible through this import
719                match import.import_type {
720                    ImportType::AllRules | ImportType::Rules | ImportType::All => {
721                        for rule in from_module.get_rules() {
722                            if from_module.exports_rule(rule)
723                                && pattern_matches(&import.pattern, rule)
724                            {
725                                has_visible = true;
726                                break;
727                            }
728                        }
729                    }
730                    ImportType::AllTemplates | ImportType::Templates => {
731                        for template in from_module.get_templates() {
732                            if from_module.exports_template(template)
733                                && pattern_matches(&import.pattern, template)
734                            {
735                                has_visible = true;
736                                break;
737                            }
738                        }
739                    }
740                }
741
742                if !has_visible {
743                    warnings.push(format!(
744                        "Import from '{}' with pattern '{}' doesn't match any exported items",
745                        import.from_module, import.pattern
746                    ));
747                }
748            }
749        }
750
751        // Check for re-export patterns that don't match any imported items
752        for import in module.get_imports() {
753            if let Some(re_export) = &import.re_export {
754                for pattern in &re_export.patterns {
755                    let mut matches_any = false;
756
757                    if let Ok(from_module) = self.get_module(&import.from_module) {
758                        // Check rules
759                        for rule in from_module.get_rules() {
760                            if from_module.exports_rule(rule) && pattern_matches(pattern, rule) {
761                                matches_any = true;
762                                break;
763                            }
764                        }
765
766                        // Check templates
767                        if !matches_any {
768                            for template in from_module.get_templates() {
769                                if from_module.exports_template(template)
770                                    && pattern_matches(pattern, template)
771                                {
772                                    matches_any = true;
773                                    break;
774                                }
775                            }
776                        }
777                    }
778
779                    if !matches_any {
780                        warnings.push(format!(
781                            "Re-export pattern '{}' from import '{}' doesn't match any items",
782                            pattern, import.from_module
783                        ));
784                    }
785                }
786            }
787        }
788
789        // Check if module has no rules or templates
790        if module.get_rules().is_empty()
791            && module.get_templates().is_empty()
792            && module.get_imports().is_empty()
793        {
794            warnings.push("Module is empty (no rules, templates, or imports)".to_string());
795        }
796
797        Ok(ModuleValidation {
798            module_name: module_name.to_string(),
799            is_valid: errors.is_empty(),
800            errors,
801            warnings,
802        })
803    }
804
805    /// Validate all modules in the system
806    pub fn validate_all_modules(&self) -> HashMap<String, ModuleValidation> {
807        self.modules
808            .keys()
809            .filter_map(|name| self.validate_module(name).ok().map(|v| (name.clone(), v)))
810            .collect()
811    }
812}
813
814impl Default for ModuleManager {
815    fn default() -> Self {
816        Self::new()
817    }
818}
819
820/// Module statistics
821#[derive(Debug, Clone)]
822pub struct ModuleStats {
823    /// Total number of modules
824    pub total_modules: usize,
825    /// Current focus module
826    pub current_focus: String,
827    /// Information about each module
828    pub modules: HashMap<String, ModuleInfo>,
829}
830
831/// Information about a single module
832#[derive(Debug, Clone)]
833pub struct ModuleInfo {
834    /// Module name
835    pub name: String,
836    /// Number of rules
837    pub rules_count: usize,
838    /// Number of templates
839    pub templates_count: usize,
840    /// Number of imports
841    pub imports_count: usize,
842    /// Export type description
843    pub exports_type: String,
844    /// Module-level salience
845    pub salience: i32,
846}
847
848/// Module validation result
849#[derive(Debug, Clone)]
850pub struct ModuleValidation {
851    /// Module name
852    pub module_name: String,
853    /// Whether the module is valid (no errors)
854    pub is_valid: bool,
855    /// List of errors
856    pub errors: Vec<String>,
857    /// List of warnings
858    pub warnings: Vec<String>,
859}
860
861/// Check if a name matches a pattern (supports wildcards)
862fn pattern_matches(pattern: &str, name: &str) -> bool {
863    if pattern == "*" || pattern == "?ALL" {
864        return true;
865    }
866
867    // Simple wildcard matching
868    if let Some(prefix) = pattern.strip_suffix('*') {
869        name.starts_with(prefix)
870    } else if let Some(suffix) = pattern.strip_prefix('*') {
871        name.ends_with(suffix)
872    } else {
873        pattern == name
874    }
875}
876
877#[cfg(test)]
878mod tests {
879    use super::*;
880
881    #[test]
882    fn test_module_creation() {
883        let mut manager = ModuleManager::new();
884
885        assert!(manager.create_module("TEST").is_ok());
886        assert!(manager.create_module("TEST").is_err()); // Duplicate
887
888        assert_eq!(manager.list_modules().len(), 2); // MAIN + TEST
889    }
890
891    #[test]
892    fn test_module_focus() {
893        let mut manager = ModuleManager::new();
894        manager.create_module("SENSORS").unwrap();
895
896        assert_eq!(manager.get_focus(), "MAIN");
897
898        manager.set_focus("SENSORS").unwrap();
899        assert_eq!(manager.get_focus(), "SENSORS");
900
901        assert!(manager.set_focus("NONEXISTENT").is_err());
902    }
903
904    #[test]
905    fn test_export_import() {
906        let mut manager = ModuleManager::new();
907        manager.create_module("SENSORS").unwrap();
908        manager.create_module("CONTROL").unwrap();
909
910        // Add rules to SENSORS
911        let sensors = manager.get_module_mut("SENSORS").unwrap();
912        sensors.add_rule("sensor-temp");
913        sensors.add_rule("sensor-pressure");
914        sensors.set_exports(ExportList::Specific(vec![ExportItem {
915            item_type: ItemType::Rule,
916            pattern: "sensor-*".to_string(),
917        }]));
918
919        // Import in CONTROL
920        manager
921            .import_from("CONTROL", "SENSORS", ImportType::AllRules, "*")
922            .unwrap();
923
924        // Check visibility
925        assert!(manager.is_rule_visible("sensor-temp", "CONTROL").unwrap());
926        assert!(manager
927            .is_rule_visible("sensor-pressure", "CONTROL")
928            .unwrap());
929    }
930
931    #[test]
932    fn test_pattern_matching() {
933        assert!(pattern_matches("*", "anything"));
934        assert!(pattern_matches("sensor-*", "sensor-temp"));
935        assert!(pattern_matches("sensor-*", "sensor-pressure"));
936        assert!(!pattern_matches("sensor-*", "control-temp"));
937        assert!(pattern_matches("*-temp", "sensor-temp"));
938        assert!(pattern_matches("exact", "exact"));
939        assert!(!pattern_matches("exact", "not-exact"));
940    }
941
942    #[test]
943    fn test_main_module_default_export() {
944        let manager = ModuleManager::new();
945        let main_module = manager.get_module("MAIN").unwrap();
946
947        // MAIN module should export all by default
948        assert!(matches!(main_module.exports, ExportList::All));
949    }
950
951    #[test]
952    fn test_user_module_default_export() {
953        let mut manager = ModuleManager::new();
954        manager.create_module("USER").unwrap();
955        let user_module = manager.get_module("USER").unwrap();
956
957        // User modules should export none by default
958        assert!(matches!(user_module.exports, ExportList::None));
959    }
960
961    #[test]
962    fn test_visibility_own_rules() {
963        let mut manager = ModuleManager::new();
964        manager.create_module("TEST").unwrap();
965
966        let test_module = manager.get_module_mut("TEST").unwrap();
967        test_module.add_rule("my-rule");
968
969        // Own rules are always visible
970        assert!(manager.is_rule_visible("my-rule", "TEST").unwrap());
971    }
972
973    #[test]
974    fn test_get_visible_rules() {
975        let mut manager = ModuleManager::new();
976        manager.create_module("MOD1").unwrap();
977        manager.create_module("MOD2").unwrap();
978
979        // Add rules to MOD1
980        let mod1 = manager.get_module_mut("MOD1").unwrap();
981        mod1.add_rule("rule1");
982        mod1.add_rule("rule2");
983        mod1.set_exports(ExportList::All);
984
985        // Add rule to MOD2
986        let mod2 = manager.get_module_mut("MOD2").unwrap();
987        mod2.add_rule("rule3");
988
989        // Import from MOD1 to MOD2
990        manager
991            .import_from("MOD2", "MOD1", ImportType::AllRules, "*")
992            .unwrap();
993
994        let visible = manager.get_visible_rules("MOD2").unwrap();
995        assert!(visible.contains(&"rule1".to_string()));
996        assert!(visible.contains(&"rule2".to_string()));
997        assert!(visible.contains(&"rule3".to_string()));
998        assert_eq!(visible.len(), 3);
999    }
1000
1001    #[test]
1002    fn test_module_stats() {
1003        let mut manager = ModuleManager::new();
1004        manager.create_module("TEST").unwrap();
1005
1006        let test_module = manager.get_module_mut("TEST").unwrap();
1007        test_module.add_rule("rule1");
1008        test_module.add_template("template1");
1009
1010        let stats = manager.get_stats();
1011        assert_eq!(stats.total_modules, 2); // MAIN + TEST
1012        assert_eq!(stats.current_focus, "MAIN");
1013
1014        let test_info = stats.modules.get("TEST").unwrap();
1015        assert_eq!(test_info.rules_count, 1);
1016        assert_eq!(test_info.templates_count, 1);
1017    }
1018
1019    // Phase 3 tests
1020
1021    #[test]
1022    fn test_transitive_reexport() {
1023        let mut manager = ModuleManager::new();
1024        manager.create_module("BASE").unwrap();
1025        manager.create_module("MIDDLE").unwrap();
1026        manager.create_module("TOP").unwrap();
1027
1028        // BASE has rules
1029        let base = manager.get_module_mut("BASE").unwrap();
1030        base.add_rule("base-rule1");
1031        base.add_rule("base-rule2");
1032        base.set_exports(ExportList::All);
1033
1034        // MIDDLE imports from BASE and re-exports
1035        manager
1036            .import_from_with_reexport(
1037                "MIDDLE",
1038                "BASE",
1039                ImportType::AllRules,
1040                "*",
1041                Some(ReExport {
1042                    patterns: vec!["base-*".to_string()],
1043                    transitive: true,
1044                }),
1045            )
1046            .unwrap();
1047
1048        // TOP imports from MIDDLE
1049        manager
1050            .import_from("TOP", "MIDDLE", ImportType::AllRules, "*")
1051            .unwrap();
1052
1053        // TOP should see rules from BASE through MIDDLE's re-export
1054        assert!(manager.is_rule_visible("base-rule1", "TOP").unwrap());
1055        assert!(manager.is_rule_visible("base-rule2", "TOP").unwrap());
1056    }
1057
1058    #[test]
1059    fn test_module_salience() {
1060        let mut manager = ModuleManager::new();
1061        manager.create_module("HIGH_PRIORITY").unwrap();
1062        manager.create_module("LOW_PRIORITY").unwrap();
1063
1064        // Set different salience levels
1065        manager.set_module_salience("HIGH_PRIORITY", 100).unwrap();
1066        manager.set_module_salience("LOW_PRIORITY", -50).unwrap();
1067
1068        // Verify salience values
1069        assert_eq!(manager.get_module_salience("HIGH_PRIORITY").unwrap(), 100);
1070        assert_eq!(manager.get_module_salience("LOW_PRIORITY").unwrap(), -50);
1071        assert_eq!(manager.get_module_salience("MAIN").unwrap(), 0);
1072
1073        // Check in stats
1074        let stats = manager.get_stats();
1075        assert_eq!(stats.modules.get("HIGH_PRIORITY").unwrap().salience, 100);
1076        assert_eq!(stats.modules.get("LOW_PRIORITY").unwrap().salience, -50);
1077    }
1078
1079    #[test]
1080    fn test_transitive_dependencies() {
1081        let mut manager = ModuleManager::new();
1082        manager.create_module("A").unwrap();
1083        manager.create_module("B").unwrap();
1084        manager.create_module("C").unwrap();
1085        manager.create_module("D").unwrap();
1086
1087        // Create dependency chain: A -> B -> C -> D
1088        manager.import_from("A", "B", ImportType::All, "*").unwrap();
1089        manager.import_from("B", "C", ImportType::All, "*").unwrap();
1090        manager.import_from("C", "D", ImportType::All, "*").unwrap();
1091
1092        // Get transitive dependencies of A
1093        let deps = manager.get_transitive_dependencies("A").unwrap();
1094        assert!(deps.contains(&"B".to_string()));
1095        assert!(deps.contains(&"C".to_string()));
1096        assert!(deps.contains(&"D".to_string()));
1097        assert_eq!(deps.len(), 3);
1098    }
1099
1100    #[test]
1101    fn test_module_validation_broken_import() {
1102        let mut manager = ModuleManager::new();
1103        manager.create_module("TEST").unwrap();
1104
1105        // Manually add a broken import (this bypasses normal validation)
1106        let test_module = manager.get_module_mut("TEST").unwrap();
1107        test_module.add_import(ImportDecl {
1108            from_module: "NONEXISTENT".to_string(),
1109            import_type: ImportType::All,
1110            pattern: "*".to_string(),
1111            re_export: None,
1112        });
1113
1114        let validation = manager.validate_module("TEST").unwrap();
1115        assert!(!validation.is_valid);
1116        assert!(validation.errors.iter().any(|e| e.contains("NONEXISTENT")));
1117    }
1118
1119    #[test]
1120    fn test_module_validation_unused_import() {
1121        let mut manager = ModuleManager::new();
1122        manager.create_module("SOURCE").unwrap();
1123        manager.create_module("TARGET").unwrap();
1124
1125        // SOURCE has a rule
1126        let source = manager.get_module_mut("SOURCE").unwrap();
1127        source.add_rule("my-rule");
1128        source.set_exports(ExportList::None); // Export nothing
1129
1130        // TARGET imports from SOURCE but SOURCE exports nothing
1131        manager
1132            .import_from("TARGET", "SOURCE", ImportType::AllRules, "*")
1133            .unwrap();
1134
1135        let validation = manager.validate_module("TARGET").unwrap();
1136        assert!(validation.is_valid); // It's valid but has warnings
1137        assert!(!validation.warnings.is_empty());
1138        assert!(validation
1139            .warnings
1140            .iter()
1141            .any(|w| w.contains("doesn't match any exported items")));
1142    }
1143
1144    #[test]
1145    fn test_module_validation_empty_module() {
1146        let mut manager = ModuleManager::new();
1147        manager.create_module("EMPTY").unwrap();
1148
1149        let validation = manager.validate_module("EMPTY").unwrap();
1150        assert!(validation.is_valid);
1151        assert!(validation.warnings.iter().any(|w| w.contains("empty")));
1152    }
1153
1154    #[test]
1155    fn test_module_validation_reexport_pattern() {
1156        let mut manager = ModuleManager::new();
1157        manager.create_module("SOURCE").unwrap();
1158        manager.create_module("TARGET").unwrap();
1159
1160        // SOURCE has rules
1161        let source = manager.get_module_mut("SOURCE").unwrap();
1162        source.add_rule("rule1");
1163        source.set_exports(ExportList::All);
1164
1165        // TARGET imports with re-export pattern that doesn't match
1166        manager
1167            .import_from_with_reexport(
1168                "TARGET",
1169                "SOURCE",
1170                ImportType::AllRules,
1171                "*",
1172                Some(ReExport {
1173                    patterns: vec!["sensor-*".to_string()], // Pattern doesn't match "rule1"
1174                    transitive: false,
1175                }),
1176            )
1177            .unwrap();
1178
1179        let validation = manager.validate_module("TARGET").unwrap();
1180        assert!(validation.is_valid);
1181        assert!(validation
1182            .warnings
1183            .iter()
1184            .any(|w| w.contains("Re-export pattern")));
1185    }
1186
1187    #[test]
1188    fn test_validate_all_modules() {
1189        let mut manager = ModuleManager::new();
1190        manager.create_module("MOD1").unwrap();
1191        manager.create_module("MOD2").unwrap();
1192
1193        let validations = manager.validate_all_modules();
1194        assert_eq!(validations.len(), 3); // MAIN + MOD1 + MOD2
1195        assert!(validations.contains_key("MAIN"));
1196        assert!(validations.contains_key("MOD1"));
1197        assert!(validations.contains_key("MOD2"));
1198    }
1199
1200    #[test]
1201    fn test_selective_reexport() {
1202        let mut manager = ModuleManager::new();
1203        manager.create_module("BASE").unwrap();
1204        manager.create_module("MIDDLE").unwrap();
1205        manager.create_module("TOP").unwrap();
1206
1207        // BASE has multiple rules
1208        let base = manager.get_module_mut("BASE").unwrap();
1209        base.add_rule("sensor-temp");
1210        base.add_rule("sensor-pressure");
1211        base.add_rule("control-valve");
1212        base.set_exports(ExportList::All);
1213
1214        // MIDDLE imports all but only re-exports sensor-* rules
1215        manager
1216            .import_from_with_reexport(
1217                "MIDDLE",
1218                "BASE",
1219                ImportType::AllRules,
1220                "*",
1221                Some(ReExport {
1222                    patterns: vec!["sensor-*".to_string()],
1223                    transitive: true,
1224                }),
1225            )
1226            .unwrap();
1227
1228        // MIDDLE should see all rules
1229        assert!(manager.is_rule_visible("sensor-temp", "MIDDLE").unwrap());
1230        assert!(manager
1231            .is_rule_visible("sensor-pressure", "MIDDLE")
1232            .unwrap());
1233        assert!(manager.is_rule_visible("control-valve", "MIDDLE").unwrap());
1234
1235        // TOP imports from MIDDLE
1236        manager
1237            .import_from("TOP", "MIDDLE", ImportType::AllRules, "*")
1238            .unwrap();
1239
1240        // TOP should only see re-exported sensor-* rules
1241        assert!(manager.is_rule_visible("sensor-temp", "TOP").unwrap());
1242        assert!(manager.is_rule_visible("sensor-pressure", "TOP").unwrap());
1243        // control-valve is not re-exported, so TOP shouldn't see it through MIDDLE
1244        // Note: This test shows the design intent, but the current implementation
1245        // may need additional logic to fully enforce transitive re-export restrictions
1246    }
1247}