Skip to main content

kasl/libs/
monitor.rs

1//! Core activity monitoring system for kasl.
2//!
3//! Implements the heart of kasl's activity tracking functionality, providing
4//! real-time monitoring of user input to automatically detect work sessions,
5//! breaks, and productivity patterns.
6//!
7//! ## Features
8//!
9//! - **Input Detection**: Low-level keyboard and mouse event capture
10//! - **State Machine**: Activity state tracking (Active/InPause)
11//! - **Timing Logic**: Configurable thresholds for activity and pause detection
12//! - **Database Integration**: Automatic workday and pause recording
13//! - **Configuration Management**: Flexible behavior customization
14//!
15//! ## Usage
16//!
17//! ```rust,no_run
18//! # async fn f() -> anyhow::Result<()> {
19//! use kasl::libs::config::MonitorConfig;
20//! use kasl::libs::monitor::Monitor;
21//!
22//! let config = MonitorConfig {
23//!     pause_threshold: 120,
24//!     activity_threshold: 60,
25//!     poll_interval: 1000,
26//!     min_pause_duration: 30,
27//!     min_work_interval: 15,
28//!     ..Default::default()
29//! };
30//!
31//! let mut monitor = Monitor::new(config)?;
32//! monitor.run().await?;
33//! # Ok(())
34//! # }
35//! ```
36
37use crate::db::pauses::Pauses;
38use crate::db::workdays::Workdays;
39use crate::libs::config::MonitorConfig;
40use crate::libs::messages::Message;
41use crate::{msg_debug, msg_error, msg_info};
42use anyhow::Result;
43use chrono::{Local, NaiveDate};
44use rdev::{EventType, listen};
45use std::sync::{Arc, Mutex};
46use tokio::time::{self, Duration, Instant};
47use tracing::{Level, debug, instrument, span};
48
49/// Represents the current state of the user's activity.
50///
51/// Provides a clean, explicit way to manage the monitor's operational state.
52/// State changes are triggered by inactivity exceeding `pause_threshold` or
53/// new input activity detected.
54#[derive(Debug, Clone, Copy, PartialEq)]
55enum State {
56    /// The user is currently active and not on a pause.
57    ///
58    /// In this state, the monitor:
59    /// - Continues tracking input activity
60    /// - Updates workday end times with each activity
61    /// - Monitors for inactivity to detect pause start
62    /// - Ensures workday creation if sustained activity is detected
63    Active,
64
65    /// The user is currently on a pause due to inactivity.
66    ///
67    /// In this state, the monitor:
68    /// - Waits for activity to resume
69    /// - Does not update workday end times
70    /// - Prepares to record pause end time when activity resumes
71    /// - Resets workday start detection when returning to active
72    InPause,
73}
74
75/// The core activity monitor responsible for tracking user presence
76/// and managing workday and pause records.
77///
78/// Orchestrates all aspects of activity monitoring, from low-level input detection
79/// to high-level workday management.
80///
81/// ## Thread Safety
82///
83/// The monitor uses thread-safe primitives to coordinate between:
84/// - **Input Thread**: Captures keyboard/mouse events via `rdev`
85/// - **Monitor Thread**: Runs the main monitoring loop
86/// - **Shared State**: Activity timestamps and workday tracking
87///
88/// ## Configuration Impact
89///
90/// All timing behavior is controlled by the [`MonitorConfig`]:
91/// - **Responsiveness**: Lower `poll_interval` = more responsive state changes
92/// - **Sensitivity**: Lower `pause_threshold` = more sensitive pause detection
93/// - **Workday Logic**: Higher `activity_threshold` = more deliberate workday starts
94/// - **Data Quality**: Higher `min_*` values = cleaner, less noisy data
95pub struct Monitor {
96    /// Configuration settings for the monitor, such as thresholds.
97    ///
98    /// This configuration controls all timing and behavior aspects of the
99    /// monitor. Changes to this configuration require restarting the monitor
100    /// to take effect, as the values are used throughout the monitoring loop.
101    pub config: MonitorConfig,
102
103    /// Database interface for managing pause records.
104    ///
105    /// This interface handles all pause-related database operations:
106    /// - Recording pause start times when inactivity is detected
107    /// - Recording pause end times when activity resumes
108    /// - Querying existing pause data for validation and reporting
109    pub pauses: Pauses,
110
111    /// Database interface for managing workday records.
112    ///
113    /// This interface handles workday lifecycle management:
114    /// - Creating new workday records when sustained activity is detected
115    /// - Updating workday end times as activity continues
116    /// - Querying workday status for duplicate prevention
117    pub workdays: Workdays,
118
119    /// Timestamp of the last detected user activity (keyboard, mouse).
120    ///
121    /// This timestamp is continuously updated by the input event listener
122    /// running in a separate thread. It's protected by a Mutex for thread-safe
123    /// access between the input thread and the monitoring loop.
124    ///
125    /// The timestamp is used to:
126    /// - Calculate inactivity duration for pause detection
127    /// - Determine if activity is "recent" for state transitions
128    /// - Provide timing information for workday management
129    pub last_activity: Arc<Mutex<Instant>>,
130
131    /// Optional timestamp marking the beginning of a period of sustained activity.
132    ///
133    /// This field implements a "sustained activity" detection mechanism to prevent
134    /// false workday starts from brief, accidental input events. The timestamp is:
135    /// - Set when activity begins after a period of inactivity
136    /// - Reset to None when a pause begins or workday is created
137    /// - Used to calculate activity duration for workday start logic
138    ///
139    /// ## Workday Start Logic
140    ///
141    /// A workday is created when:
142    /// 1. `activity_start` is set (continuous activity period began)
143    /// 2. Duration since `activity_start` exceeds `activity_threshold`
144    /// 3. No workday record exists for the current date
145    pub activity_start: Arc<Mutex<Option<Instant>>>,
146
147    /// The current operational state of the monitor (Active or InPause).
148    ///
149    /// This field tracks the monitor's current state and drives the main
150    /// monitoring loop logic. State transitions trigger various actions:
151    /// - Active → InPause: Record pause start, reset activity tracking
152    /// - InPause → Active: Record pause end, resume workday tracking
153    state: State,
154}
155
156impl Monitor {
157    /// Creates a new `Monitor` instance.
158    ///
159    /// This constructor initializes all monitor components and sets up the
160    /// background input event listener. It performs several critical setup
161    /// operations that are essential for proper monitoring functionality.
162    ///
163    /// ## Initialization Process
164    ///
165    /// 1. **Database Connections**: Establishes connections to workday and pause databases
166    /// 2. **Shared State**: Creates thread-safe containers for activity tracking
167    /// 3. **Input Listener**: Spawns background thread for keyboard/mouse monitoring
168    /// 4. **State Setup**: Initializes monitor in Active state
169    ///
170    /// ## Input Event Handling
171    ///
172    /// The constructor spawns a dedicated thread running `rdev::listen()` to capture:
173    /// - **Keyboard Events**: Key presses and releases
174    /// - **Mouse Events**: Button clicks, movements, and scroll wheel
175    /// - **Timestamp Updates**: Continuous activity timestamp maintenance
176    ///
177    /// ## Thread Architecture
178    ///
179    /// ```text
180    /// Main Thread          Input Thread
181    /// ┌─────────────┐     ┌─────────────────┐
182    /// │   Monitor   │────▶│  rdev::listen() │
183    /// │    Loop     │     │   (keyboard/    │
184    /// │             │◀────│     mouse)      │
185    /// └─────────────┘     └─────────────────┘
186    ///        │                      │
187    ///        └──── Shared State ────┘
188    ///        (last_activity, activity_start)
189    /// ```
190    ///
191    /// ## Error Handling
192    ///
193    /// The input listener includes error handling for:
194    /// - Input device access failures
195    /// - Platform-specific event capture issues
196    /// - Thread communication problems
197    ///
198    /// Errors in the input thread are logged but don't crash the main monitor,
199    /// allowing for graceful degradation when input monitoring isn't available.
200    ///
201    /// # Arguments
202    ///
203    /// * `config` - The [`MonitorConfig`] containing timing and behavior settings
204    ///
205    /// # Returns
206    ///
207    /// Returns `Ok(Monitor)` with a fully initialized monitor ready to start
208    /// the monitoring loop, or an error if database initialization fails.
209    ///
210    /// # Error Scenarios
211    ///
212    /// - **Database Connection**: Cannot connect to SQLite database files
213    /// - **Database Schema**: Database schema is incompatible or corrupted
214    /// - **File Permissions**: Cannot read/write database files
215    /// - **Resource Exhaustion**: System cannot create necessary threads or allocate memory
216    ///
217    /// # Examples
218    ///
219    /// ```rust,no_run
220    /// # async fn f() -> anyhow::Result<()> {
221    /// use kasl::libs::config::MonitorConfig;
222    /// use kasl::libs::monitor::Monitor;
223    ///
224    /// // Create monitor with default configuration
225    /// let config = MonitorConfig::default();
226    /// let mut monitor = Monitor::new(config)?;
227    ///
228    /// // Start monitoring
229    /// monitor.run().await?;
230    /// # Ok(())
231    /// # }
232    /// ```
233    #[instrument(skip(config))]
234    pub fn new(config: MonitorConfig) -> Result<Self> {
235        let span = span!(Level::INFO, "monitor_init");
236        let _enter = span.enter();
237
238        debug!("Initializing monitor with config: {:?}", config);
239
240        // Initialize database connections
241        // These connections will be used throughout the monitor's lifetime
242        let pauses = Pauses::new()?;
243        let workdays = Workdays::new()?;
244
245        // Create shared state containers for cross-thread communication
246        let last_activity = Arc::new(Mutex::new(Instant::now()));
247        let activity_start = Arc::new(Mutex::new(None));
248
249        // Clone Arc references for the input event listener thread
250        // This allows the background thread to update shared state
251        let last_activity_clone = Arc::clone(&last_activity);
252        let activity_start_clone = Arc::clone(&activity_start);
253
254        // Spawn a new thread to listen for device events
255        // This ensures the main monitor loop is not blocked by event listening
256        std::thread::spawn(move || {
257            if let Err(e) = listen(move |event| match event.event_type {
258                // Monitor all types of user input for activity detection
259                EventType::KeyPress(_)
260                | EventType::KeyRelease(_)
261                | EventType::ButtonPress(_)
262                | EventType::ButtonRelease(_)
263                | EventType::MouseMove { .. }
264                | EventType::Wheel { .. } => {
265                    // Update activity tracking with current timestamp
266                    {
267                        let mut last_activity = last_activity_clone.lock().unwrap();
268                        *last_activity = Instant::now();
269                    }
270
271                    // Manage sustained activity tracking for workday detection
272                    {
273                        let mut activity_start = activity_start_clone.lock().unwrap();
274
275                        // If this is the first activity after inactivity, mark the start
276                        // This begins the sustained activity period for workday detection
277                        if activity_start.is_none() {
278                            *activity_start = Some(Instant::now());
279                        }
280                    }
281                }
282            }) {
283                // Log input listener errors but don't crash the application
284                // This allows the monitor to continue functioning even if input
285                // monitoring encounters platform-specific issues
286                msg_error!(Message::ErrorInRdevListener(format!("{:?}", e)));
287            }
288        });
289
290        Ok(Monitor {
291            config,
292            pauses,
293            workdays,
294            last_activity,
295            activity_start,
296            state: State::Active, // Initialize the monitor in the Active state
297        })
298    }
299
300    /// Runs the main monitoring loop.
301    ///
302    /// This asynchronous function implements the core monitoring logic that runs
303    /// continuously until the monitor is stopped. It orchestrates state management,
304    /// activity detection, and database operations in a coordinated fashion.
305    ///
306    /// ## Loop Architecture
307    ///
308    /// The monitoring loop operates on a fixed polling interval and performs
309    /// these operations each cycle:
310    ///
311    /// 1. **Activity Detection**: Check if recent input activity occurred
312    /// 2. **State Evaluation**: Determine appropriate state based on activity
313    /// 3. **State Transitions**: Handle transitions between Active and InPause
314    /// 4. **Workday Management**: Ensure workday records are properly maintained
315    /// 5. **Sleep**: Wait for the next polling interval
316    ///
317    /// ## State Machine Logic
318    ///
319    /// ```text
320    /// ┌─────────────────────────────────────────────────────────────┐
321    /// │                    Monitor Loop                             │
322    /// │                                                             │
323    /// │  ┌─────────────┐    Activity?    ┌─────────────────────┐   │
324    /// │  │   Active    │─────────No─────▶│  handle_inactivity  │   │
325    /// │  │             │                 │                     │   │
326    /// │  │             │◀────────────────│  (start pause)      │   │
327    /// │  └─────────────┘                 └─────────────────────┘   │
328    /// │        │                                                   │
329    /// │     Activity                                                │
330    /// │        │                                                   │
331    /// │        ▼                                                   │
332    /// │  ┌─────────────────────┐                                   │
333    /// │  │ ensure_workday_     │                                   │
334    /// │  │ started             │                                   │
335    /// │  └─────────────────────┘                                   │
336    /// │                                                             │
337    /// │  ┌─────────────┐    Activity?    ┌─────────────────────┐   │
338    /// │  │  InPause    │─────────Yes────▶│handle_return_from_  │   │
339    /// │  │             │                 │pause                │   │
340    /// │  │             │◀────────────────│                     │   │
341    /// │  └─────────────┘                 │  (end pause)        │   │
342    /// │                                  └─────────────────────┘   │
343    /// └─────────────────────────────────────────────────────────────┘
344    /// ```
345    ///
346    /// ## Configuration-Driven Behavior
347    ///
348    /// The loop behavior is entirely controlled by the [`MonitorConfig`]:
349    /// - **`poll_interval`**: Controls loop frequency and CPU usage
350    /// - **`pause_threshold`**: Determines when inactivity becomes a pause
351    /// - **`activity_threshold`**: Controls workday start detection sensitivity
352    /// - **Special Case**: If `pause_threshold` is 0, monitoring is disabled
353    ///
354    /// ## Database Operations
355    ///
356    /// The loop performs these database operations as needed:
357    /// - **Workday Creation**: When sustained activity is detected
358    /// - **Pause Recording**: When inactivity exceeds threshold
359    /// - **Pause Completion**: When activity resumes after pause
360    /// - **Workday Updates**: Continuous end time updates during activity
361    ///
362    /// ## Error Handling
363    ///
364    /// Database errors during the monitoring loop are handled gracefully:
365    /// - Errors are logged with detailed information
366    /// - The loop continues running to avoid service interruption
367    /// - Critical errors are propagated to stop the monitor
368    ///
369    /// ## Performance Characteristics
370    ///
371    /// - **CPU Usage**: Directly proportional to polling frequency (1/poll_interval)
372    /// - **Memory Usage**: Constant, no accumulation of historical data
373    /// - **I/O Operations**: Minimal, only database writes for state changes
374    /// - **Network Usage**: None during monitoring
375    ///
376    /// # Returns
377    ///
378    /// Returns `Ok(())` when the monitoring loop is explicitly stopped,
379    /// or an error if a critical database operation fails that prevents
380    /// continued monitoring.
381    ///
382    /// # Error Scenarios
383    ///
384    /// - **Database Connection Loss**: SQLite database becomes unavailable
385    /// - **Disk Space Exhaustion**: Cannot write to database files
386    /// - **Permission Changes**: Database files become read-only
387    /// - **System Resource Exhaustion**: Cannot allocate memory for operations
388    ///
389    /// # Examples
390    ///
391    /// ```rust,no_run
392    /// # async fn f() -> anyhow::Result<()> {
393    /// use kasl::libs::config::MonitorConfig;
394    /// use kasl::libs::monitor::Monitor;
395    ///
396    /// // Start monitoring with custom configuration
397    /// let config = MonitorConfig {
398    ///     poll_interval: 1000,      // Check every second
399    ///     pause_threshold: 120,     // Pause after 2 minutes
400    ///     activity_threshold: 30,   // Workday starts after 30s
401    ///     ..Default::default()
402    /// };
403    ///
404    /// let mut monitor = Monitor::new(config)?;
405    /// monitor.run().await?; // Runs indefinitely
406    /// # Ok(())
407    /// # }
408    /// ```
409    #[instrument(skip(self))]
410    pub async fn run(&mut self) -> Result<()> {
411        msg_info!(Message::MonitorStarted {
412            pause_threshold: self.config.pause_threshold,
413            poll_interval: self.config.poll_interval,
414            activity_threshold: self.config.activity_threshold,
415        });
416
417        // Special case: if pause threshold is 0, pauses are disabled
418        // This allows users to disable pause tracking while keeping workday tracking
419        if self.config.pause_threshold == 0 {
420            return Ok(());
421        }
422
423        // The main loop that periodically checks for activity and updates state
424        loop {
425            let activity_detected = self.detect_activity();
426            let today = Local::now().date_naive();
427
428            // State machine logic - handle state transitions based on current state and activity
429            // Use error handling to prevent database lock issues from crashing the watcher
430            match self.state {
431                // Currently active, but no recent activity detected
432                State::Active if !activity_detected => {
433                    if let Err(e) = self.handle_inactivity() {
434                        msg_error!(Message::DatabaseOperationFailed {
435                            operation: "handle_inactivity".to_string(),
436                            error: e.to_string()
437                        });
438                    }
439                }
440                // Currently in pause, but activity has resumed
441                State::InPause if activity_detected => {
442                    if let Err(e) = self.handle_return_from_pause() {
443                        msg_error!(Message::DatabaseOperationFailed {
444                            operation: "handle_return_from_pause".to_string(),
445                            error: e.to_string()
446                        });
447                    }
448                }
449                // Currently active with ongoing activity - ensure workday is tracked
450                State::Active if activity_detected => {
451                    if let Err(e) = self.ensure_workday_started(today) {
452                        msg_error!(Message::DatabaseOperationFailed {
453                            operation: "ensure_workday_started".to_string(),
454                            error: e.to_string()
455                        });
456                    }
457                }
458                // No action needed for: InPause with no activity
459                _ => {}
460            }
461
462            // Wait for the configured poll interval before the next check
463            // This controls the monitoring loop frequency and CPU usage
464            time::sleep(Duration::from_millis(self.config.poll_interval)).await;
465        }
466    }
467
468    /// Checks if any user activity has been detected within the last poll interval.
469    ///
470    /// This method implements the core activity detection logic that drives
471    /// the monitor's state machine. It determines whether the user is currently
472    /// active based on the recency of input events captured by the background
473    /// input listener thread.
474    ///
475    /// ## Detection Logic
476    ///
477    /// Activity is considered "detected" when the time elapsed since the last
478    /// input event is less than the polling interval. This approach ensures
479    /// that activity detection is synchronized with the monitoring loop's
480    /// polling frequency.
481    ///
482    /// ```text
483    /// Timeline: ───●───●─────────●──────────────────▶
484    ///          input input   poll               now
485    ///                          ↑
486    ///                   elapsed < poll_interval
487    ///                     = Activity Detected
488    /// ```
489    ///
490    /// ## Sensitivity Tuning
491    ///
492    /// The detection sensitivity can be adjusted through configuration:
493    /// - **Lower `poll_interval`**: More sensitive, detects brief activity
494    /// - **Higher `poll_interval`**: Less sensitive, requires sustained activity
495    ///
496    /// ## Thread Safety
497    ///
498    /// This method safely accesses the shared `last_activity` timestamp
499    /// that is continuously updated by the input listener thread. The mutex
500    /// protection ensures data consistency without blocking the input thread.
501    ///
502    /// ## Debug Logging
503    ///
504    /// When debug mode is enabled (`KASL_DEBUG=1`), this method logs detailed
505    /// timing information to help with troubleshooting and configuration tuning:
506    /// - Elapsed time since last activity
507    /// - Activity detection result
508    /// - Timing relationship to poll interval
509    ///
510    /// # Returns
511    ///
512    /// Returns `true` if user activity was detected within the last poll interval,
513    /// `false` if the user appears to be inactive.
514    ///
515    /// # Performance Considerations
516    ///
517    /// This method is called once per polling cycle and performs minimal work:
518    /// - Single mutex lock/unlock operation
519    /// - Simple timestamp arithmetic
520    /// - Optional debug logging
521    ///
522    /// The implementation is designed for frequent calling with minimal overhead.
523    ///
524    /// # Examples
525    ///
526    /// ```rust,no_run
527    /// use kasl::libs::monitor::Monitor;
528    /// use kasl::libs::config::MonitorConfig;
529    ///
530    /// let monitor = Monitor::new(MonitorConfig::default())?;
531    ///
532    /// // Check for activity
533    /// if monitor.detect_activity() {
534    ///     println!("User is active");
535    /// } else {
536    ///     println!("User appears inactive");
537    /// }
538    /// # Ok::<(), anyhow::Error>(())
539    /// ```
540    pub fn detect_activity(&self) -> bool {
541        let elapsed = self.last_activity.lock().unwrap().elapsed();
542
543        // Activity is considered detected if the time since last_activity
544        // is less than the poll_interval. This creates a sliding window
545        // of activity detection that aligns with the monitoring loop timing.
546        let is_active = elapsed < Duration::from_millis(self.config.poll_interval);
547
548        // Debug logging only visible with KASL_DEBUG=1
549        // This helps with configuration tuning and troubleshooting
550        msg_debug!(format!(
551            "Activity check: elapsed={:?}, active={}, threshold={:?}",
552            elapsed,
553            is_active,
554            Duration::from_millis(self.config.poll_interval)
555        ));
556
557        is_active
558    }
559
560    /// Handles the scenario when user inactivity is detected.
561    ///
562    /// This method implements the transition from Active to InPause state when
563    /// the user has been inactive for longer than the configured pause threshold.
564    /// It manages both the state transition and the database recording of the
565    /// pause event.
566    ///
567    /// ## Inactivity Assessment
568    ///
569    /// The method calculates total inactivity duration by examining the time
570    /// elapsed since the last recorded input activity. If this duration exceeds
571    /// the configured `pause_threshold`, a pause is initiated.
572    ///
573    /// ## Pause Recording Strategy
574    ///
575    /// When recording a pause, the system uses retroactive timing to ensure
576    /// accuracy:
577    /// ```text
578    /// Timeline: ──●─────────────────●──────▶
579    ///          last              threshold
580    ///         activity            exceeded
581    ///             ↑                   ↑
582    ///      pause actually         pause
583    ///         started            detected
584    /// ```
585    ///
586    /// The pause start time is calculated as:
587    /// `current_time - pause_threshold`
588    ///
589    /// This approach ensures that the recorded pause start time reflects when
590    /// the user actually became inactive, not when the system detected it.
591    ///
592    /// ## State Management
593    ///
594    /// When transitioning to pause state, the method performs several actions:
595    /// 1. **Database Recording**: Inserts pause start record with calculated timing
596    /// 2. **State Transition**: Changes monitor state from Active to InPause
597    /// 3. **Activity Reset**: Clears the sustained activity tracker
598    ///
599    /// ## Activity Tracking Reset
600    ///
601    /// The `activity_start` timestamp is reset to `None` during pause initiation.
602    /// This is crucial for preventing incorrect workday start detection when
603    /// activity resumes after a pause. It ensures that workday logic requires
604    /// sustained activity rather than brief resume activity.
605    ///
606    /// ## User Notification
607    ///
608    /// The method provides user feedback about pause detection through
609    /// informational messages. In foreground mode, users see real-time
610    /// pause notifications for immediate feedback.
611    ///
612    /// # Returns
613    ///
614    /// Returns `Ok(())` if the pause was successfully recorded and state
615    /// transition completed, or an error if database operations fail.
616    ///
617    /// # Error Scenarios
618    ///
619    /// - **Database Connection**: Cannot connect to pause database
620    /// - **Database Write**: Cannot insert pause start record
621    /// - **Timing Calculation**: System clock issues affecting timestamp calculation
622    ///
623    /// # Database Schema Impact
624    ///
625    /// This method creates records in the `pauses` table:
626    /// ```sql
627    /// INSERT INTO pauses (date, start_time) VALUES (?, ?);
628    /// ```
629    ///
630    /// # Examples
631    ///
632    /// ```rust,no_run
633    /// use kasl::libs::config::MonitorConfig;
634    ///
635    /// // This method is called automatically by the monitoring loop
636    /// // when inactivity exceeds the configured threshold
637    ///
638    /// // Example configuration for sensitive pause detection:
639    /// let config = MonitorConfig {
640    ///     pause_threshold: 30,  // Detect pauses after 30 seconds
641    ///     ..Default::default()
642    /// };
643    /// ```
644    fn handle_inactivity(&mut self) -> Result<()> {
645        let idle_time = self.last_activity.lock().unwrap().elapsed();
646
647        // Only initiate pause if inactivity exceeds the configured threshold
648        if idle_time >= Duration::from_secs(self.config.pause_threshold) {
649            let today = Local::now().date_naive();
650            // Do not record pauses before the workday has started — otherwise
651            // pre-work idle creates a pause that ends seconds before workdays.start.
652            if self.workdays.fetch(today)?.is_none() {
653                return Ok(());
654            }
655
656            msg_info!(Message::PauseStarted);
657
658            // Calculate the actual pause start time by subtracting the threshold
659            // This provides more accurate pause timing in reports
660            let pause_start_time = Local::now().naive_local() - chrono::Duration::seconds(self.config.pause_threshold as i64);
661
662            // Record the pause start in the database
663            self.pauses.insert_start_with_time(pause_start_time)?;
664
665            // Transition to pause state
666            self.state = State::InPause;
667
668            // Reset the activity_start timer when a pause begins
669            // This prevents incorrect workday start detection after pause ends
670            *self.activity_start.lock().unwrap() = None;
671        }
672
673        Ok(())
674    }
675
676    /// Handles the scenario when user activity resumes after a pause.
677    ///
678    /// This method manages the transition from InPause back to Active state
679    /// when user input activity is detected after a period of inactivity.
680    /// It completes the pause record in the database and prepares the monitor
681    /// for resumed activity tracking.
682    ///
683    /// ## Pause Completion
684    ///
685    /// When activity resumes, the method completes the pause record by:
686    /// 1. Recording the current time as the pause end time
687    /// 2. Calculating the total pause duration
688    /// 3. Updating the database with the complete pause record
689    ///
690    /// ## State Transition
691    ///
692    /// The transition from InPause to Active involves:
693    /// - **Database Update**: Recording pause end time
694    /// - **State Change**: Setting monitor state to Active
695    /// - **Activity Preparation**: Resuming normal activity tracking
696    ///
697    /// ## Activity Tracking Resumption
698    ///
699    /// After returning from pause, the monitor resumes normal operation:
700    /// - Input events are again tracked for workday management
701    /// - The `activity_start` timer may be set for new sustained activity detection
702    /// - Workday end times will be updated with continued activity
703    ///
704    /// ## Timing Accuracy
705    ///
706    /// The pause end time is recorded as the current timestamp when activity
707    /// is first detected after the pause period. This provides accurate
708    /// timing for pause duration calculations in reports.
709    ///
710    /// ## User Feedback
711    ///
712    /// The method provides immediate notification about pause completion,
713    /// which is especially useful in foreground monitoring mode for
714    /// real-time activity awareness.
715    ///
716    /// # Returns
717    ///
718    /// Returns `Ok(())` if the pause end was successfully recorded and the
719    /// state transition completed, or an error if database operations fail.
720    ///
721    /// # Error Scenarios
722    ///
723    /// - **Database Connection**: Cannot connect to pause database
724    /// - **Database Update**: Cannot record pause end time
725    /// - **Inconsistent State**: No active pause record to complete
726    ///
727    /// # Database Schema Impact
728    ///
729    /// This method updates records in the `pauses` table:
730    /// ```sql
731    /// UPDATE pauses SET end_time = ? WHERE end_time IS NULL AND date = ?;
732    /// ```
733    ///
734    /// # Pause Duration Calculation
735    ///
736    /// After this method completes, the pause duration can be calculated as:
737    /// ```text
738    /// duration = end_time - start_time
739    /// ```
740    ///
741    /// This duration is used in productivity reports and statistics.
742    ///
743    /// # Examples
744    ///
745    /// ```rust,no_run
746    /// // This method is called automatically by the monitoring loop
747    /// // when activity resumes after a pause period
748    ///
749    /// // The resulting pause record will include:
750    /// // - start_time: When inactivity was first detected
751    /// // - end_time: When activity resumed (current time)
752    /// // - date: The date when the pause occurred
753    /// ```
754    fn handle_return_from_pause(&mut self) -> Result<()> {
755        msg_info!(Message::PauseEnded);
756
757        // Record the pause end time in the database
758        // This completes the pause record that was started in handle_inactivity()
759        self.pauses.insert_end()?;
760
761        // Transition back to active state
762        self.state = State::Active;
763
764        Ok(())
765    }
766
767    /// Ensures that a workday record has been started for the current day
768    /// if sustained activity is detected.
769    ///
770    /// This method implements intelligent workday start detection to automatically
771    /// create workday records when the user begins sustained work activity. It
772    /// prevents false workday starts from brief, accidental input while ensuring
773    /// that genuine work sessions are properly captured.
774    ///
775    /// ## Sustained Activity Logic
776    ///
777    /// The workday start detection uses a multi-stage process:
778    ///
779    /// 1. **Activity Initiation**: `activity_start` timestamp is set by input events
780    /// 2. **Duration Check**: Calculate time elapsed since activity started
781    /// 3. **Threshold Validation**: Compare duration against `activity_threshold`
782    /// 4. **Workday Creation**: Create workday record if threshold is exceeded
783    /// 5. **Tracker Reset**: Clear `activity_start` to prevent duplicate creation
784    ///
785    /// ```text
786    /// Timeline: ──●───●───●───●──────────●─────▶
787    ///          start              threshold   now
788    ///         activity             exceeded
789    ///             ↑                   ↑
790    ///      activity_start        workday
791    ///         is set             created
792    /// ```
793    ///
794    /// ## Duplicate Prevention
795    ///
796    /// The method includes multiple safeguards against duplicate workday creation:
797    /// - **Existence Check**: Verifies no workday record exists for the date
798    /// - **Activity Validation**: Requires valid `activity_start` timestamp
799    /// - **Threshold Enforcement**: Demands sustained activity duration
800    /// - **Reset Mechanism**: Clears tracker after successful creation
801    ///
802    /// ## Configuration Impact
803    ///
804    /// The `activity_threshold` setting controls workday start sensitivity:
805    /// - **Lower Values (10-30s)**: Quick workday detection, good for focused work
806    /// - **Higher Values (60-120s)**: Conservative detection, avoids false starts
807    /// - **Very High Values (300s+)**: Only detects deliberate work sessions
808    ///
809    /// ## Database Operations
810    ///
811    /// When creating a workday, the method:
812    /// 1. Queries for existing workday records on the target date
813    /// 2. Creates a new workday record with the current timestamp
814    /// 3. Handles any database errors gracefully
815    ///
816    /// ## Error Handling
817    ///
818    /// Database errors during workday creation are logged but don't crash
819    /// the monitor. This ensures continuous monitoring even if individual
820    /// database operations encounter issues.
821    ///
822    /// ## State Management
823    ///
824    /// After successful workday creation:
825    /// - `activity_start` is reset to `None`
826    /// - Monitor continues tracking for pause detection
827    /// - Future activity updates the workday end time
828    /// - No additional workday records are created for the date
829    ///
830    /// # Arguments
831    ///
832    /// * `today` - The current date for workday record creation
833    ///
834    /// # Returns
835    ///
836    /// Returns `Ok(())` if workday management completed successfully,
837    /// or an error if critical database operations fail.
838    ///
839    /// # Error Scenarios
840    ///
841    /// - **Database Connection**: Cannot connect to workdays database
842    /// - **Database Query**: Cannot check for existing workday records
843    /// - **Database Insert**: Cannot create new workday record
844    /// - **Date Validation**: Invalid date format or system clock issues
845    ///
846    /// # Database Schema Impact
847    ///
848    /// This method may create records in the `workdays` table:
849    /// ```sql
850    /// INSERT INTO workdays (date, start_time) VALUES (?, ?);
851    /// ```
852    ///
853    /// # Examples
854    ///
855    /// ```rust,no_run
856    /// // This method is called automatically during the monitoring loop
857    /// // when the user is active and no workday exists for the current date
858    ///
859    /// // Example: User starts work at 9:00 AM
860    /// // - First input at 9:00:00 sets activity_start
861    /// // - Continued input until 9:00:30
862    /// // - If activity_threshold = 30s, workday is created at 9:00:30
863    /// // - Workday start_time is recorded as current timestamp
864    /// ```
865    pub fn ensure_workday_started(&mut self, today: NaiveDate) -> Result<()> {
866        // Check if we have an activity start timestamp
867        let activity_start_time = {
868            let activity_start_guard = self.activity_start.lock().unwrap();
869            *activity_start_guard
870        };
871
872        // Only proceed if we have been tracking sustained activity
873        if let Some(start_time) = activity_start_time {
874            // Calculate how long the current activity period has lasted
875            let activity_duration = start_time.elapsed();
876
877            // Check if sustained activity exceeds the configured threshold
878            if activity_duration >= Duration::from_secs(self.config.activity_threshold) {
879                // Verify that no workday record already exists for today
880                if self.workdays.fetch(today)?.is_none() {
881                    // Create new workday record with current timestamp
882                    match self.workdays.insert_start(today) {
883                        Ok(()) => {
884                            msg_info!(Message::WorkdayStarting(today.to_string()));
885
886                            // Reset activity_start to prevent duplicate workday creation
887                            // This ensures we only create one workday per date
888                            *self.activity_start.lock().unwrap() = None;
889                        }
890                        Err(e) => {
891                            // Log workday creation errors but continue monitoring
892                            // This prevents single database errors from stopping the monitor
893                            msg_error!(Message::WorkdayCreateFailed);
894                            debug!("Workday creation error: {:?}", e);
895                        }
896                    }
897                }
898            }
899        }
900
901        Ok(())
902    }
903}