1use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum BrainWave {
14 Gamma,
17 Beta,
20 Alpha,
23 Theta,
26 Delta,
29}
30
31impl BrainWave {
32 #[must_use]
34 pub const fn allows_tools(self) -> bool {
35 matches!(self, Self::Gamma | Self::Beta | Self::Alpha)
36 }
37
38 #[must_use]
40 pub const fn allows_consolidation(self) -> bool {
41 matches!(self, Self::Theta)
42 }
43
44 #[must_use]
46 pub const fn is_dormant(self) -> bool {
47 matches!(self, Self::Delta)
48 }
49
50 #[must_use]
52 pub const fn name(self) -> &'static str {
53 match self {
54 Self::Gamma => "Gamma (active)",
55 Self::Beta => "Beta (working)",
56 Self::Alpha => "Alpha (idle)",
57 Self::Theta => "Theta (drowsy)",
58 Self::Delta => "Delta (dormant)",
59 }
60 }
61}
62
63impl fmt::Display for BrainWave {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}", self.name())
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct BrainWaveConfig {
72 pub gamma_rate: f64,
74 pub alpha_idle: Duration,
76 pub theta_idle: Duration,
78 pub delta_idle: Duration,
80}
81
82impl Default for BrainWaveConfig {
83 fn default() -> Self {
84 Self {
85 gamma_rate: 10.0,
86 alpha_idle: Duration::from_secs(30),
87 theta_idle: Duration::from_secs(300),
88 delta_idle: Duration::from_secs(1800),
89 }
90 }
91}
92
93impl BrainWaveConfig {
94 #[must_use]
102 pub fn from_env() -> Self {
103 let defaults = Self::default();
104 Self {
105 gamma_rate: std::env::var("WM_GAMMA_RATE")
106 .ok()
107 .and_then(|v| v.parse().ok())
108 .unwrap_or(defaults.gamma_rate),
109 alpha_idle: Duration::from_secs(
110 std::env::var("WM_ALPHA_IDLE")
111 .ok()
112 .and_then(|v| v.parse().ok())
113 .unwrap_or(defaults.alpha_idle.as_secs()),
114 ),
115 theta_idle: Duration::from_secs(
116 std::env::var("WM_THETA_IDLE")
117 .ok()
118 .and_then(|v| v.parse().ok())
119 .unwrap_or(defaults.theta_idle.as_secs()),
120 ),
121 delta_idle: Duration::from_secs(
122 std::env::var("WM_DELTA_IDLE")
123 .ok()
124 .and_then(|v| v.parse().ok())
125 .unwrap_or(defaults.delta_idle.as_secs()),
126 ),
127 }
128 }
129}
130
131pub struct BrainWaveTracker {
136 pub config: BrainWaveConfig,
138 timestamps: smallvec::SmallVec<[Instant; 64]>,
139 last_event: Instant,
140 current: BrainWave,
141}
142
143impl BrainWaveTracker {
144 #[must_use]
146 pub fn new(config: BrainWaveConfig) -> Self {
147 let now = Instant::now();
148 Self {
149 config,
150 timestamps: smallvec::SmallVec::new(),
151 last_event: now,
152 current: BrainWave::Delta,
153 }
154 }
155
156 pub fn record_event(&mut self) -> BrainWave {
158 let now = Instant::now();
159 self.last_event = now;
160 self.timestamps.push(now);
161 let cutoff = now.checked_sub(Duration::from_secs(60)).unwrap();
163 self.timestamps.retain(|t| *t > cutoff);
164 self.recompute(now);
165 self.current
166 }
167
168 pub fn recompute(&mut self, now: Instant) -> BrainWave {
171 let rate = self.event_rate(now);
172 let idle = now.duration_since(self.last_event);
173
174 self.current = if rate > self.config.gamma_rate {
175 BrainWave::Gamma
176 } else if rate > 0.0 {
177 BrainWave::Beta
178 } else if idle > self.config.delta_idle {
179 BrainWave::Delta
180 } else if idle > self.config.theta_idle {
181 BrainWave::Theta
182 } else {
183 BrainWave::Alpha
184 };
185
186 self.current
187 }
188
189 #[must_use]
191 pub const fn current(&self) -> BrainWave {
192 self.current
193 }
194
195 #[must_use]
201 pub fn next_transition_duration(&self) -> Duration {
202 let now = Instant::now();
203 let idle = now.duration_since(self.last_event);
204 match self.current {
205 BrainWave::Gamma | BrainWave::Beta => self.config.alpha_idle.saturating_sub(idle),
206 BrainWave::Alpha => self.config.theta_idle.saturating_sub(idle),
207 BrainWave::Theta => self.config.delta_idle.saturating_sub(idle),
208 BrainWave::Delta => Duration::from_secs(3600),
209 }
210 }
211
212 #[must_use]
214 pub fn idle_duration(&self) -> Duration {
215 Instant::now().duration_since(self.last_event)
216 }
217
218 fn event_rate(&self, now: Instant) -> f64 {
220 if self.timestamps.is_empty() {
221 return 0.0;
222 }
223 let cutoff = now.checked_sub(Duration::from_secs(60)).unwrap();
224 let recent = self.timestamps.iter().filter(|&&t| t > cutoff).count();
225 recent as f64
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn brain_wave_allows_tools() {
235 assert!(BrainWave::Gamma.allows_tools());
236 assert!(BrainWave::Beta.allows_tools());
237 assert!(BrainWave::Alpha.allows_tools());
238 assert!(!BrainWave::Theta.allows_tools());
239 assert!(!BrainWave::Delta.allows_tools());
240 }
241
242 #[test]
243 fn tracker_starts_dormant() {
244 let tracker = BrainWaveTracker::new(BrainWaveConfig::default());
245 assert_eq!(tracker.current(), BrainWave::Delta);
246 }
247
248 #[test]
249 fn tracker_transitions_on_event() {
250 let mut tracker = BrainWaveTracker::new(BrainWaveConfig::default());
251 let _ = tracker.record_event();
252 assert_eq!(tracker.current(), BrainWave::Beta);
253 }
254
255 #[test]
256 fn tracker_gamma_on_burst() {
257 let mut tracker = BrainWaveTracker::new(BrainWaveConfig::default());
258 for _ in 0..15 {
259 let _ = tracker.record_event();
260 }
261 assert_eq!(tracker.current(), BrainWave::Gamma);
262 }
263
264 #[test]
265 fn next_transition_duration_beta_to_alpha() {
266 let mut tracker = BrainWaveTracker::new(BrainWaveConfig::default());
267 let _ = tracker.record_event();
268 assert_eq!(tracker.current(), BrainWave::Beta);
269 let d = tracker.next_transition_duration();
270 assert!(d <= Duration::from_secs(30));
272 assert!(d > Duration::from_secs(28));
273 }
274
275 #[test]
276 fn next_transition_duration_delta_sleeps_long() {
277 let tracker = BrainWaveTracker::new(BrainWaveConfig::default());
278 assert_eq!(tracker.current(), BrainWave::Delta);
279 let d = tracker.next_transition_duration();
280 assert_eq!(d, Duration::from_secs(3600));
281 }
282
283 #[test]
284 fn idle_duration_grows_after_event() {
285 let mut tracker = BrainWaveTracker::new(BrainWaveConfig::default());
286 let _ = tracker.record_event();
287 let d1 = tracker.idle_duration();
288 std::thread::sleep(Duration::from_millis(50));
289 let d2 = tracker.idle_duration();
290 assert!(d2 > d1);
291 }
292}