1use crate::Result;
7use colored::Colorize;
8use comfy_table::{Cell, Row, Table};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::fmt;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
15pub enum DebugLevel {
16 Trace,
18 Debug,
20 Info,
22 Warn,
24 Error,
26}
27
28impl fmt::Display for DebugLevel {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::Trace => write!(f, "TRACE"),
32 Self::Debug => write!(f, "DEBUG"),
33 Self::Info => write!(f, "INFO "),
34 Self::Warn => write!(f, "WARN "),
35 Self::Error => write!(f, "ERROR"),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct DebugMessage {
43 pub level: DebugLevel,
45 pub message: String,
47 pub location: Option<String>,
49 pub timestamp: chrono::DateTime<chrono::Utc>,
51 pub context: HashMap<String, String>,
53}
54
55impl DebugMessage {
56 pub fn new(level: DebugLevel, message: impl Into<String>) -> Self {
58 Self {
59 level,
60 message: message.into(),
61 location: None,
62 timestamp: chrono::Utc::now(),
63 context: HashMap::new(),
64 }
65 }
66
67 pub fn with_location(mut self, location: impl Into<String>) -> Self {
69 self.location = Some(location.into());
70 self
71 }
72
73 pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
75 self.context.insert(key.into(), value.into());
76 self
77 }
78
79 pub fn format_colored(&self) -> String {
81 let level_str = match self.level {
82 DebugLevel::Trace => format!("{}", self.level).dimmed(),
83 DebugLevel::Debug => format!("{}", self.level).cyan(),
84 DebugLevel::Info => format!("{}", self.level).green(),
85 DebugLevel::Warn => format!("{}", self.level).yellow(),
86 DebugLevel::Error => format!("{}", self.level).red().bold(),
87 };
88
89 let time = self.timestamp.format("%H:%M:%S%.3f");
90 let location = self
91 .location
92 .as_ref()
93 .map(|l| format!(" [{}]", l))
94 .unwrap_or_default();
95
96 format!("{} {} {}{}", time, level_str, self.message, location)
97 }
98}
99
100pub struct Debugger {
102 messages: Vec<DebugMessage>,
104 level_filter: DebugLevel,
106 max_messages: usize,
108}
109
110impl Debugger {
111 pub fn new() -> Self {
113 Self {
114 messages: Vec::new(),
115 level_filter: DebugLevel::Debug,
116 max_messages: 1000,
117 }
118 }
119
120 pub fn set_level_filter(&mut self, level: DebugLevel) {
122 self.level_filter = level;
123 }
124
125 pub fn set_max_messages(&mut self, max: usize) {
127 self.max_messages = max;
128 if self.messages.len() > max {
129 self.messages.drain(0..(self.messages.len() - max));
130 }
131 }
132
133 pub fn log(&mut self, message: DebugMessage) {
135 if message.level >= self.level_filter {
136 self.messages.push(message);
137
138 if self.messages.len() > self.max_messages {
140 self.messages.remove(0);
141 }
142 }
143 }
144
145 pub fn trace(&mut self, message: impl Into<String>) {
147 self.log(DebugMessage::new(DebugLevel::Trace, message));
148 }
149
150 pub fn debug(&mut self, message: impl Into<String>) {
152 self.log(DebugMessage::new(DebugLevel::Debug, message));
153 }
154
155 pub fn info(&mut self, message: impl Into<String>) {
157 self.log(DebugMessage::new(DebugLevel::Info, message));
158 }
159
160 pub fn warn(&mut self, message: impl Into<String>) {
162 self.log(DebugMessage::new(DebugLevel::Warn, message));
163 }
164
165 pub fn error(&mut self, message: impl Into<String>) {
167 self.log(DebugMessage::new(DebugLevel::Error, message));
168 }
169
170 pub fn messages(&self) -> &[DebugMessage] {
172 &self.messages
173 }
174
175 pub fn messages_by_level(&self, level: DebugLevel) -> Vec<&DebugMessage> {
177 self.messages.iter().filter(|m| m.level == level).collect()
178 }
179
180 pub fn clear(&mut self) {
182 self.messages.clear();
183 }
184
185 pub fn report(&self) -> String {
187 let mut report = String::new();
188 report.push_str(&format!("\n{}\n", "Debug Report".bold()));
189 report.push_str(&format!("{}\n\n", "=".repeat(60)));
190
191 if self.messages.is_empty() {
192 report.push_str("No debug messages\n");
193 return report;
194 }
195
196 let mut counts = HashMap::new();
198 for msg in &self.messages {
199 *counts.entry(msg.level).or_insert(0) += 1;
200 }
201
202 report.push_str(&format!("{}\n", "Message counts:".bold()));
203 for level in [
204 DebugLevel::Trace,
205 DebugLevel::Debug,
206 DebugLevel::Info,
207 DebugLevel::Warn,
208 DebugLevel::Error,
209 ] {
210 let count = counts.get(&level).unwrap_or(&0);
211 report.push_str(&format!(" {}: {}\n", level, count));
212 }
213
214 report.push_str(&format!("\n{}\n", "Recent messages:".bold()));
215 let recent_count = self.messages.len().min(20);
216 for msg in &self.messages[self.messages.len() - recent_count..] {
217 report.push_str(&format!(" {}\n", msg.format_colored()));
218 }
219
220 report
221 }
222
223 pub fn export_json(&self) -> Result<String> {
225 Ok(serde_json::to_string_pretty(&self.messages)?)
226 }
227}
228
229impl Default for Debugger {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235pub struct DataInspector;
237
238impl DataInspector {
239 pub fn inspect_array(data: &[f64], name: &str) -> String {
241 let mut report = String::new();
242 report.push_str(&format!(
243 "\n{}\n",
244 format!("Array Inspection: {}", name).bold()
245 ));
246 report.push_str(&format!("{}\n\n", "=".repeat(60)));
247
248 if data.is_empty() {
249 report.push_str("Empty array\n");
250 return report;
251 }
252
253 let len = data.len();
254 let (min, max) = data
255 .iter()
256 .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), &v| {
257 (min.min(v), max.max(v))
258 });
259 let sum: f64 = data.iter().sum();
260 let mean = sum / len as f64;
261
262 let variance = data.iter().map(|&v| (v - mean).powi(2)).sum::<f64>() / len as f64;
263 let std_dev = variance.sqrt();
264
265 let mut table = Table::new();
266 table.add_row(Row::from(vec![
267 Cell::new("Length"),
268 Cell::new(format!("{}", len)),
269 ]));
270 table.add_row(Row::from(vec![
271 Cell::new("Min"),
272 Cell::new(format!("{:.6}", min)),
273 ]));
274 table.add_row(Row::from(vec![
275 Cell::new("Max"),
276 Cell::new(format!("{:.6}", max)),
277 ]));
278 table.add_row(Row::from(vec![
279 Cell::new("Mean"),
280 Cell::new(format!("{:.6}", mean)),
281 ]));
282 table.add_row(Row::from(vec![
283 Cell::new("Std Dev"),
284 Cell::new(format!("{:.6}", std_dev)),
285 ]));
286
287 report.push_str(&table.to_string());
288 report.push('\n');
289
290 report.push_str(&format!("\n{}\n", "Sample values:".bold()));
292 let sample_count = len.min(10);
293 for (i, &v) in data.iter().take(sample_count).enumerate() {
294 report.push_str(&format!(" [{}] = {:.6}\n", i, v));
295 }
296
297 if len > sample_count {
298 report.push_str(&format!(" ... ({} more values)\n", len - sample_count));
299 }
300
301 report
302 }
303
304 pub fn inspect_map(data: &HashMap<String, String>, name: &str) -> String {
306 let mut report = String::new();
307 report.push_str(&format!(
308 "\n{}\n",
309 format!("Map Inspection: {}", name).bold()
310 ));
311 report.push_str(&format!("{}\n\n", "=".repeat(60)));
312
313 if data.is_empty() {
314 report.push_str("Empty map\n");
315 return report;
316 }
317
318 report.push_str(&format!("Entries: {}\n\n", data.len()));
319
320 let mut table = Table::new();
321 table.set_header(Row::from(vec![Cell::new("Key"), Cell::new("Value")]));
322
323 let mut keys: Vec<_> = data.keys().collect();
324 keys.sort();
325
326 for key in keys {
327 if let Some(value) = data.get(key) {
328 let display_value = if value.len() > 50 {
329 format!("{}...", &value[..50])
330 } else {
331 value.clone()
332 };
333 table.add_row(Row::from(vec![Cell::new(key), Cell::new(display_value)]));
334 }
335 }
336
337 report.push_str(&table.to_string());
338 report.push('\n');
339
340 report
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_debug_level_ordering() {
350 assert!(DebugLevel::Trace < DebugLevel::Debug);
351 assert!(DebugLevel::Debug < DebugLevel::Info);
352 assert!(DebugLevel::Info < DebugLevel::Warn);
353 assert!(DebugLevel::Warn < DebugLevel::Error);
354 }
355
356 #[test]
357 fn test_debug_message_creation() {
358 let msg = DebugMessage::new(DebugLevel::Info, "test message");
359 assert_eq!(msg.level, DebugLevel::Info);
360 assert_eq!(msg.message, "test message");
361 }
362
363 #[test]
364 fn test_debugger_logging() {
365 let mut debugger = Debugger::new();
366 debugger.info("info message");
367 debugger.warn("warn message");
368
369 assert_eq!(debugger.messages().len(), 2);
370 }
371
372 #[test]
373 fn test_debugger_level_filter() {
374 let mut debugger = Debugger::new();
375 debugger.set_level_filter(DebugLevel::Warn);
376
377 debugger.debug("debug message");
378 debugger.warn("warn message");
379 debugger.error("error message");
380
381 assert_eq!(debugger.messages().len(), 2);
383 }
384
385 #[test]
386 fn test_debugger_max_messages() {
387 let mut debugger = Debugger::new();
388 debugger.set_max_messages(5);
389
390 for i in 0..10 {
391 debugger.info(format!("message {}", i));
392 }
393
394 assert_eq!(debugger.messages().len(), 5);
395 }
396
397 #[test]
398 fn test_debugger_messages_by_level() {
399 let mut debugger = Debugger::new();
400 debugger.info("info1");
401 debugger.warn("warn1");
402 debugger.info("info2");
403
404 let info_msgs = debugger.messages_by_level(DebugLevel::Info);
405 assert_eq!(info_msgs.len(), 2);
406 }
407
408 #[test]
409 fn test_data_inspector_array() {
410 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
411 let report = DataInspector::inspect_array(&data, "test");
412 assert!(report.contains("Array Inspection"));
413 assert!(report.contains("Length"));
414 }
415
416 #[test]
417 fn test_data_inspector_empty_array() {
418 let data: Vec<f64> = vec![];
419 let report = DataInspector::inspect_array(&data, "empty");
420 assert!(report.contains("Empty array"));
421 }
422
423 #[test]
424 fn test_data_inspector_map() {
425 let mut data = HashMap::new();
426 data.insert("key1".to_string(), "value1".to_string());
427 data.insert("key2".to_string(), "value2".to_string());
428
429 let report = DataInspector::inspect_map(&data, "test");
430 assert!(report.contains("Map Inspection"));
431 assert!(report.contains("key1"));
432 }
433}