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}