1use crate::types::{ActionType, LogicalOperator, Operator, Value};
2use chrono::{DateTime, Utc};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
7pub enum ConditionExpression {
8    Field(String),
10    FunctionCall {
12        name: String,
14        args: Vec<String>,
16    },
17}
18
19#[derive(Debug, Clone)]
21pub struct Condition {
22    pub expression: ConditionExpression,
24    pub operator: Operator,
26    pub value: Value,
28
29    #[deprecated(note = "Use expression instead")]
31    #[doc(hidden)]
32    pub field: String,
33}
34
35impl Condition {
36    pub fn new(field: String, operator: Operator, value: Value) -> Self {
38        Self {
39            expression: ConditionExpression::Field(field.clone()),
40            operator,
41            value,
42            field, }
44    }
45
46    pub fn with_function(
48        function_name: String,
49        args: Vec<String>,
50        operator: Operator,
51        value: Value,
52    ) -> Self {
53        Self {
54            expression: ConditionExpression::FunctionCall {
55                name: function_name.clone(),
56                args,
57            },
58            operator,
59            value,
60            field: function_name, }
62    }
63
64    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
66        match &self.expression {
67            ConditionExpression::Field(field_name) => {
68                if let Some(field_value) = get_nested_value(facts, field_name) {
69                    self.operator.evaluate(field_value, &self.value)
70                } else {
71                    false
72                }
73            }
74            ConditionExpression::FunctionCall { .. } => {
75                false
77            }
78        }
79    }
80
81    pub fn evaluate_with_engine(
84        &self,
85        facts: &HashMap<String, Value>,
86        function_registry: &HashMap<
87            String,
88            std::sync::Arc<dyn Fn(Vec<Value>, &HashMap<String, Value>) -> crate::errors::Result<Value> + Send + Sync>,
89        >,
90    ) -> bool {
91        match &self.expression {
92            ConditionExpression::Field(field_name) => {
93                if let Some(field_value) = get_nested_value(facts, field_name) {
94                    self.operator.evaluate(field_value, &self.value)
95                } else {
96                    false
97                }
98            }
99            ConditionExpression::FunctionCall { name, args } => {
100                if let Some(function) = function_registry.get(name) {
102                    let arg_values: Vec<Value> = args
104                        .iter()
105                        .map(|arg| {
106                            get_nested_value(facts, arg)
107                                .cloned()
108                                .unwrap_or(Value::String(arg.clone()))
109                        })
110                        .collect();
111
112                    if let Ok(result) = function(arg_values, facts) {
114                        return self.operator.evaluate(&result, &self.value);
116                    }
117                }
118                false
119            }
120        }
121    }
122}
123
124#[derive(Debug, Clone)]
126pub enum ConditionGroup {
127    Single(Condition),
129    Compound {
131        left: Box<ConditionGroup>,
133        operator: LogicalOperator,
135        right: Box<ConditionGroup>,
137    },
138    Not(Box<ConditionGroup>),
140    Exists(Box<ConditionGroup>),
142    Forall(Box<ConditionGroup>),
144}
145
146impl ConditionGroup {
147    pub fn single(condition: Condition) -> Self {
149        ConditionGroup::Single(condition)
150    }
151
152    pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
154        ConditionGroup::Compound {
155            left: Box::new(left),
156            operator: LogicalOperator::And,
157            right: Box::new(right),
158        }
159    }
160
161    pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
163        ConditionGroup::Compound {
164            left: Box::new(left),
165            operator: LogicalOperator::Or,
166            right: Box::new(right),
167        }
168    }
169
170    #[allow(clippy::should_implement_trait)]
172    pub fn not(condition: ConditionGroup) -> Self {
173        ConditionGroup::Not(Box::new(condition))
174    }
175
176    pub fn exists(condition: ConditionGroup) -> Self {
178        ConditionGroup::Exists(Box::new(condition))
179    }
180
181    pub fn forall(condition: ConditionGroup) -> Self {
183        ConditionGroup::Forall(Box::new(condition))
184    }
185
186    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
188        match self {
189            ConditionGroup::Single(condition) => condition.evaluate(facts),
190            ConditionGroup::Compound {
191                left,
192                operator,
193                right,
194            } => {
195                let left_result = left.evaluate(facts);
196                let right_result = right.evaluate(facts);
197                match operator {
198                    LogicalOperator::And => left_result && right_result,
199                    LogicalOperator::Or => left_result || right_result,
200                    LogicalOperator::Not => !left_result, }
202            }
203            ConditionGroup::Not(condition) => !condition.evaluate(facts),
204            ConditionGroup::Exists(_) | ConditionGroup::Forall(_) => {
205                false
208            }
209        }
210    }
211
212    pub fn evaluate_with_facts(&self, facts: &crate::engine::facts::Facts) -> bool {
214        use crate::engine::pattern_matcher::PatternMatcher;
215
216        match self {
217            ConditionGroup::Single(condition) => {
218                let fact_map = facts.get_all_facts();
219                condition.evaluate(&fact_map)
220            }
221            ConditionGroup::Compound {
222                left,
223                operator,
224                right,
225            } => {
226                let left_result = left.evaluate_with_facts(facts);
227                let right_result = right.evaluate_with_facts(facts);
228                match operator {
229                    LogicalOperator::And => left_result && right_result,
230                    LogicalOperator::Or => left_result || right_result,
231                    LogicalOperator::Not => !left_result,
232                }
233            }
234            ConditionGroup::Not(condition) => !condition.evaluate_with_facts(facts),
235            ConditionGroup::Exists(condition) => PatternMatcher::evaluate_exists(condition, facts),
236            ConditionGroup::Forall(condition) => PatternMatcher::evaluate_forall(condition, facts),
237        }
238    }
239}
240
241#[derive(Debug, Clone)]
243pub struct Rule {
244    pub name: String,
246    pub description: Option<String>,
248    pub salience: i32,
250    pub enabled: bool,
252    pub no_loop: bool,
254    pub lock_on_active: bool,
256    pub agenda_group: Option<String>,
258    pub activation_group: Option<String>,
260    pub date_effective: Option<DateTime<Utc>>,
262    pub date_expires: Option<DateTime<Utc>>,
264    pub conditions: ConditionGroup,
266    pub actions: Vec<ActionType>,
268}
269
270impl Rule {
271    pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
273        Self {
274            name,
275            description: None,
276            salience: 0,
277            enabled: true,
278            no_loop: false,
279            lock_on_active: false,
280            agenda_group: None,
281            activation_group: None,
282            date_effective: None,
283            date_expires: None,
284            conditions,
285            actions,
286        }
287    }
288
289    pub fn with_description(mut self, description: String) -> Self {
291        self.description = Some(description);
292        self
293    }
294
295    pub fn with_salience(mut self, salience: i32) -> Self {
297        self.salience = salience;
298        self
299    }
300
301    pub fn with_priority(mut self, priority: i32) -> Self {
303        self.salience = priority;
304        self
305    }
306
307    pub fn with_no_loop(mut self, no_loop: bool) -> Self {
309        self.no_loop = no_loop;
310        self
311    }
312
313    pub fn with_lock_on_active(mut self, lock_on_active: bool) -> Self {
315        self.lock_on_active = lock_on_active;
316        self
317    }
318
319    pub fn with_agenda_group(mut self, agenda_group: String) -> Self {
321        self.agenda_group = Some(agenda_group);
322        self
323    }
324
325    pub fn with_activation_group(mut self, activation_group: String) -> Self {
327        self.activation_group = Some(activation_group);
328        self
329    }
330
331    pub fn with_date_effective(mut self, date_effective: DateTime<Utc>) -> Self {
333        self.date_effective = Some(date_effective);
334        self
335    }
336
337    pub fn with_date_expires(mut self, date_expires: DateTime<Utc>) -> Self {
339        self.date_expires = Some(date_expires);
340        self
341    }
342
343    pub fn with_date_effective_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
345        let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
346        self.date_effective = Some(date);
347        Ok(self)
348    }
349
350    pub fn with_date_expires_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
352        let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
353        self.date_expires = Some(date);
354        Ok(self)
355    }
356
357    pub fn is_active_at(&self, timestamp: DateTime<Utc>) -> bool {
359        if let Some(effective) = self.date_effective {
361            if timestamp < effective {
362                return false;
363            }
364        }
365
366        if let Some(expires) = self.date_expires {
368            if timestamp >= expires {
369                return false;
370            }
371        }
372
373        true
374    }
375
376    pub fn is_active(&self) -> bool {
378        self.is_active_at(Utc::now())
379    }
380
381    pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
383        self.enabled && self.conditions.evaluate(facts)
384    }
385}
386
387#[derive(Debug, Clone)]
389pub struct RuleExecutionResult {
390    pub rule_name: String,
392    pub matched: bool,
394    pub actions_executed: Vec<String>,
396    pub execution_time_ms: f64,
398}
399
400impl RuleExecutionResult {
401    pub fn new(rule_name: String) -> Self {
403        Self {
404            rule_name,
405            matched: false,
406            actions_executed: Vec::new(),
407            execution_time_ms: 0.0,
408        }
409    }
410
411    pub fn matched(mut self) -> Self {
413        self.matched = true;
414        self
415    }
416
417    pub fn with_actions(mut self, actions: Vec<String>) -> Self {
419        self.actions_executed = actions;
420        self
421    }
422
423    pub fn with_execution_time(mut self, time_ms: f64) -> Self {
425        self.execution_time_ms = time_ms;
426        self
427    }
428}
429
430fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
432    let parts: Vec<&str> = path.split('.').collect();
433    let mut current = data.get(parts[0])?;
434
435    for part in parts.iter().skip(1) {
436        match current {
437            Value::Object(obj) => {
438                current = obj.get(*part)?;
439            }
440            _ => return None,
441        }
442    }
443
444    Some(current)
445}