mockforge_core/persona_lifecycle_time.rs
1//! Lifecycle time manager for automatic lifecycle state updates
2//!
3//! This module provides integration between time travel and persona lifecycle states.
4//! When virtual time advances, it automatically checks and updates persona lifecycle
5//! states based on transition rules.
6
7use crate::time_travel::{get_global_clock, VirtualClock};
8use chrono::{DateTime, Utc};
9#[cfg(feature = "data")]
10use mockforge_data::persona_lifecycle::PersonaLifecycle;
11use std::sync::Arc;
12use tracing::{debug, info, warn};
13
14/// Manager for updating persona lifecycles when time changes
15///
16/// This manager registers callbacks with the virtual clock to automatically
17/// update persona lifecycle states when virtual time advances.
18pub struct LifecycleTimeManager {
19 /// Callback to update persona lifecycles
20 /// Takes (old_time, new_time) and returns list of updated personas
21 update_callback: Arc<dyn Fn(DateTime<Utc>, DateTime<Utc>) -> Vec<String> + Send + Sync>,
22}
23
24impl LifecycleTimeManager {
25 /// Create a new lifecycle time manager
26 ///
27 /// # Arguments
28 /// * `update_callback` - Function that updates persona lifecycles and returns list of updated persona IDs
29 pub fn new<F>(update_callback: F) -> Self
30 where
31 F: Fn(DateTime<Utc>, DateTime<Utc>) -> Vec<String> + Send + Sync + 'static,
32 {
33 Self {
34 update_callback: Arc::new(update_callback),
35 }
36 }
37
38 /// Register with the global virtual clock
39 ///
40 /// This will automatically update persona lifecycles whenever time changes.
41 pub fn register_with_clock(&self) {
42 if let Some(clock) = get_global_clock() {
43 self.register_with_clock_instance(&clock);
44 } else {
45 warn!("No global virtual clock found, lifecycle time manager not registered");
46 }
47 }
48
49 /// Register with a specific virtual clock instance
50 ///
51 /// This allows registering with a clock that may not be in the global registry.
52 pub fn register_with_clock_instance(&self, clock: &VirtualClock) {
53 let callback = self.update_callback.clone();
54 clock.on_time_change(move |old_time, new_time| {
55 debug!("Time changed from {} to {}, updating persona lifecycles", old_time, new_time);
56 let updated = callback(old_time, new_time);
57 if !updated.is_empty() {
58 info!("Updated {} persona lifecycle states: {:?}", updated.len(), updated);
59 }
60 });
61 info!("LifecycleTimeManager registered with virtual clock");
62 }
63}
64
65/// Check if a persona lifecycle should transition based on elapsed time
66///
67/// # Arguments
68/// * `lifecycle` - The persona lifecycle to check
69/// * `current_time` - The current virtual time
70///
71/// # Returns
72/// `true` if the lifecycle state was updated, `false` otherwise
73pub fn check_and_update_lifecycle_transitions(
74 lifecycle: &mut PersonaLifecycle,
75 current_time: DateTime<Utc>,
76) -> bool {
77 let old_state = lifecycle.current_state;
78 let elapsed = current_time - lifecycle.state_entered_at;
79
80 // Check each transition rule
81 for rule in &lifecycle.transition_rules {
82 // Check if enough time has passed
83 if let Some(after_days) = rule.after_days {
84 let required_duration = chrono::Duration::days(after_days as i64);
85 if elapsed < required_duration {
86 continue; // Not enough time has passed
87 }
88 }
89
90 // Check condition if present (simplified - in production would need proper condition evaluation)
91 if let Some(condition) = &rule.condition {
92 // For now, we'll skip condition evaluation and just check time
93 // In production, this would evaluate the condition against persona metadata
94 debug!("Skipping condition evaluation: {}", condition);
95 }
96
97 // Transition to the new state
98 lifecycle.current_state = rule.to;
99 lifecycle.state_entered_at = current_time;
100 lifecycle.state_history.push((current_time, rule.to));
101
102 info!(
103 "Persona {} lifecycle transitioned: {:?} -> {:?}",
104 lifecycle.persona_id, old_state, rule.to
105 );
106
107 return true; // State was updated
108 }
109
110 false // No transition occurred
111}