1use crate::conflict_detector::FileConflictInfo;
12use crate::models::{GeneratedFile, ValidationResult};
13use crate::review_engine::ReviewResult;
14use serde::{Deserialize, Serialize};
15use std::time::Duration;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct GenerationStats {
20 pub tokens_used: usize,
22 pub time_elapsed: Duration,
24 pub files_generated: usize,
26 pub lines_generated: usize,
28 pub conflicts_detected: usize,
30 pub conflicts_resolved: usize,
32}
33
34impl Default for GenerationStats {
35 fn default() -> Self {
36 Self {
37 tokens_used: 0,
38 time_elapsed: Duration::ZERO,
39 files_generated: 0,
40 lines_generated: 0,
41 conflicts_detected: 0,
42 conflicts_resolved: 0,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct GenerationResult {
50 pub files: Vec<GeneratedFile>,
52 pub validation: ValidationResult,
54 pub review: Option<ReviewResult>,
56 pub conflicts: Vec<FileConflictInfo>,
58 pub stats: GenerationStats,
60}
61
62impl GenerationResult {
63 pub fn new(
65 files: Vec<GeneratedFile>,
66 validation: ValidationResult,
67 conflicts: Vec<FileConflictInfo>,
68 stats: GenerationStats,
69 ) -> Self {
70 Self {
71 files,
72 validation,
73 review: None,
74 conflicts,
75 stats,
76 }
77 }
78
79 pub fn with_review(mut self, review: ReviewResult) -> Self {
81 self.review = Some(review);
82 self
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct GenerationReport {
89 pub title: String,
91 pub timestamp: String,
93 pub summary: ReportSummary,
95 pub file_stats: FileStatistics,
97 pub validation_report: ValidationReport,
99 pub conflict_report: ConflictReport,
101 pub review_report: Option<ReviewReport>,
103 pub performance: PerformanceMetrics,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ReportSummary {
110 pub success: bool,
112 pub status: String,
114 pub files_generated: usize,
116 pub lines_generated: usize,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct FileStatistics {
123 pub total_files: usize,
125 pub files_by_language: std::collections::HashMap<String, usize>,
127 pub total_lines: usize,
129 pub average_lines_per_file: f64,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ValidationReport {
136 pub passed: bool,
138 pub error_count: usize,
140 pub warning_count: usize,
142 pub errors_by_file: std::collections::HashMap<String, usize>,
144 pub warnings_by_file: std::collections::HashMap<String, usize>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ConflictReport {
151 pub total_conflicts: usize,
153 pub conflicts_resolved: usize,
155 pub conflicts_pending: usize,
157 pub conflicts_by_strategy: std::collections::HashMap<String, usize>,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct ReviewReport {
164 pub quality_score: f64,
166 pub suggestion_count: usize,
168 pub issue_count: usize,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct PerformanceMetrics {
175 pub time_elapsed_seconds: f64,
177 pub tokens_used: usize,
179 pub files_per_second: f64,
181 pub lines_per_second: f64,
183}
184
185pub struct ReportGenerator;
187
188impl ReportGenerator {
189 pub fn generate(result: &GenerationResult) -> GenerationReport {
191 let timestamp = chrono::Local::now().to_rfc3339();
192
193 let file_stats = Self::calculate_file_stats(&result.files);
195
196 let validation_report = Self::calculate_validation_report(&result.validation);
198
199 let conflict_report = Self::calculate_conflict_report(&result.conflicts);
201
202 let performance = Self::calculate_performance(&result.stats);
204
205 let review_report = result.review.as_ref().map(|review| ReviewReport {
207 quality_score: (review.overall_score * 100.0) as f64,
208 suggestion_count: review.suggestions.len(),
209 issue_count: review.issues.len(),
210 });
211
212 let success = result.validation.valid && result.conflicts.is_empty();
214 let status = if success {
215 "Generation completed successfully".to_string()
216 } else if !result.validation.valid {
217 format!(
218 "Generation completed with {} validation errors",
219 result.validation.errors.len()
220 )
221 } else {
222 format!(
223 "Generation completed with {} conflicts detected",
224 result.conflicts.len()
225 )
226 };
227
228 GenerationReport {
229 title: "Code Generation Report".to_string(),
230 timestamp,
231 summary: ReportSummary {
232 success,
233 status,
234 files_generated: result.files.len(),
235 lines_generated: file_stats.total_lines,
236 },
237 file_stats,
238 validation_report,
239 conflict_report,
240 review_report,
241 performance,
242 }
243 }
244
245 pub fn generate_text(result: &GenerationResult) -> String {
247 let report = Self::generate(result);
248 Self::format_report(&report)
249 }
250
251 pub fn generate_json(result: &GenerationResult) -> Result<String, serde_json::Error> {
253 let report = Self::generate(result);
254 serde_json::to_string_pretty(&report)
255 }
256
257 fn calculate_file_stats(files: &[GeneratedFile]) -> FileStatistics {
258 let mut files_by_language = std::collections::HashMap::new();
259 let mut total_lines = 0;
260
261 for file in files {
262 let line_count = file.content.lines().count();
263 total_lines += line_count;
264
265 *files_by_language.entry(file.language.clone()).or_insert(0) += 1;
266 }
267
268 let average_lines_per_file = if files.is_empty() {
269 0.0
270 } else {
271 total_lines as f64 / files.len() as f64
272 };
273
274 FileStatistics {
275 total_files: files.len(),
276 files_by_language,
277 total_lines,
278 average_lines_per_file,
279 }
280 }
281
282 fn calculate_validation_report(validation: &ValidationResult) -> ValidationReport {
283 let mut errors_by_file = std::collections::HashMap::new();
284 let mut warnings_by_file = std::collections::HashMap::new();
285
286 for error in &validation.errors {
287 *errors_by_file.entry(error.file.clone()).or_insert(0) += 1;
288 }
289
290 for warning in &validation.warnings {
291 *warnings_by_file.entry(warning.file.clone()).or_insert(0) += 1;
292 }
293
294 ValidationReport {
295 passed: validation.valid,
296 error_count: validation.errors.len(),
297 warning_count: validation.warnings.len(),
298 errors_by_file,
299 warnings_by_file,
300 }
301 }
302
303 fn calculate_conflict_report(conflicts: &[FileConflictInfo]) -> ConflictReport {
304 let mut conflicts_by_strategy = std::collections::HashMap::new();
305
306 if !conflicts.is_empty() {
308 conflicts_by_strategy.insert("Pending".to_string(), conflicts.len());
309 }
310
311 ConflictReport {
312 total_conflicts: conflicts.len(),
313 conflicts_resolved: 0, conflicts_pending: conflicts.len(),
315 conflicts_by_strategy,
316 }
317 }
318
319 fn calculate_performance(stats: &GenerationStats) -> PerformanceMetrics {
320 let time_elapsed_seconds = stats.time_elapsed.as_secs_f64();
321 let files_per_second = if time_elapsed_seconds > 0.0 {
322 stats.files_generated as f64 / time_elapsed_seconds
323 } else {
324 0.0
325 };
326 let lines_per_second = if time_elapsed_seconds > 0.0 {
327 stats.lines_generated as f64 / time_elapsed_seconds
328 } else {
329 0.0
330 };
331
332 PerformanceMetrics {
333 time_elapsed_seconds,
334 tokens_used: stats.tokens_used,
335 files_per_second,
336 lines_per_second,
337 }
338 }
339
340 fn format_report(report: &GenerationReport) -> String {
341 let mut output = String::new();
342
343 output.push_str("╔════════════════════════════════════════════════════════════╗\n");
344 output.push_str(&format!(
345 "║ {} ║\n",
346 report.title
347 ));
348 output.push_str("╚════════════════════════════════════════════════════════════╝\n\n");
349
350 output.push_str(&format!("Timestamp: {}\n\n", report.timestamp));
351
352 output.push_str("SUMMARY\n");
354 output.push_str("───────────────────────────────────────────────────────────────\n");
355 output.push_str(&format!("Status: {}\n", report.summary.status));
356 output.push_str(&format!(
357 "Files Generated: {}\n",
358 report.summary.files_generated
359 ));
360 output.push_str(&format!(
361 "Lines Generated: {}\n\n",
362 report.summary.lines_generated
363 ));
364
365 output.push_str("FILE STATISTICS\n");
367 output.push_str("───────────────────────────────────────────────────────────────\n");
368 output.push_str(&format!("Total Files: {}\n", report.file_stats.total_files));
369 output.push_str(&format!("Total Lines: {}\n", report.file_stats.total_lines));
370 output.push_str(&format!(
371 "Average Lines per File: {:.2}\n",
372 report.file_stats.average_lines_per_file
373 ));
374
375 if !report.file_stats.files_by_language.is_empty() {
376 output.push_str("\nFiles by Language:\n");
377 for (lang, count) in &report.file_stats.files_by_language {
378 output.push_str(&format!(" {}: {}\n", lang, count));
379 }
380 }
381 output.push('\n');
382
383 output.push_str("VALIDATION RESULTS\n");
385 output.push_str("───────────────────────────────────────────────────────────────\n");
386 output.push_str(&format!(
387 "Status: {}\n",
388 if report.validation_report.passed {
389 "PASSED"
390 } else {
391 "FAILED"
392 }
393 ));
394 output.push_str(&format!(
395 "Errors: {}\n",
396 report.validation_report.error_count
397 ));
398 output.push_str(&format!(
399 "Warnings: {}\n\n",
400 report.validation_report.warning_count
401 ));
402
403 output.push_str("CONFLICT DETECTION\n");
405 output.push_str("───────────────────────────────────────────────────────────────\n");
406 output.push_str(&format!(
407 "Total Conflicts: {}\n",
408 report.conflict_report.total_conflicts
409 ));
410 output.push_str(&format!(
411 "Conflicts Resolved: {}\n",
412 report.conflict_report.conflicts_resolved
413 ));
414 output.push_str(&format!(
415 "Conflicts Pending: {}\n\n",
416 report.conflict_report.conflicts_pending
417 ));
418
419 output.push_str("PERFORMANCE METRICS\n");
421 output.push_str("───────────────────────────────────────────────────────────────\n");
422 output.push_str(&format!(
423 "Time Elapsed: {:.2}s\n",
424 report.performance.time_elapsed_seconds
425 ));
426 output.push_str(&format!(
427 "Tokens Used: {}\n",
428 report.performance.tokens_used
429 ));
430 output.push_str(&format!(
431 "Files per Second: {:.2}\n",
432 report.performance.files_per_second
433 ));
434 output.push_str(&format!(
435 "Lines per Second: {:.2}\n\n",
436 report.performance.lines_per_second
437 ));
438
439 if let Some(review) = &report.review_report {
441 output.push_str("CODE REVIEW\n");
442 output.push_str("───────────────────────────────────────────────────────────────\n");
443 output.push_str(&format!("Quality Score: {:.1}/100\n", review.quality_score));
444 output.push_str(&format!("Suggestions: {}\n", review.suggestion_count));
445 output.push_str(&format!("Issues: {}\n\n", review.issue_count));
446 }
447
448 output.push_str("═══════════════════════════════════════════════════════════════\n");
449
450 output
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn test_generation_stats_default() {
460 let stats = GenerationStats::default();
461 assert_eq!(stats.tokens_used, 0);
462 assert_eq!(stats.files_generated, 0);
463 assert_eq!(stats.lines_generated, 0);
464 assert_eq!(stats.conflicts_detected, 0);
465 assert_eq!(stats.conflicts_resolved, 0);
466 }
467
468 #[test]
469 fn test_generation_result_creation() {
470 let files = vec![GeneratedFile {
471 path: "test.rs".to_string(),
472 content: "fn main() {}".to_string(),
473 language: "rust".to_string(),
474 }];
475 let validation = ValidationResult::default();
476 let conflicts = vec![];
477 let stats = GenerationStats {
478 files_generated: 1,
479 lines_generated: 1,
480 ..Default::default()
481 };
482
483 let result = GenerationResult::new(files, validation, conflicts, stats);
484 assert_eq!(result.files.len(), 1);
485 assert!(result.validation.valid);
486 assert!(result.conflicts.is_empty());
487 }
488
489 #[test]
490 fn test_report_generation() {
491 let files = vec![
492 GeneratedFile {
493 path: "test1.rs".to_string(),
494 content: "fn main() {\n println!(\"Hello\");\n}".to_string(),
495 language: "rust".to_string(),
496 },
497 GeneratedFile {
498 path: "test2.rs".to_string(),
499 content: "fn helper() {}".to_string(),
500 language: "rust".to_string(),
501 },
502 ];
503 let validation = ValidationResult::default();
504 let conflicts = vec![];
505 let stats = GenerationStats {
506 files_generated: 2,
507 lines_generated: 4,
508 tokens_used: 100,
509 time_elapsed: Duration::from_secs(1),
510 ..Default::default()
511 };
512
513 let result = GenerationResult::new(files, validation, conflicts, stats);
514 let report = ReportGenerator::generate(&result);
515
516 assert_eq!(report.summary.files_generated, 2);
517 assert_eq!(report.summary.lines_generated, 4);
518 assert_eq!(report.file_stats.total_files, 2);
519 assert_eq!(report.performance.tokens_used, 100);
520 }
521
522 #[test]
523 fn test_report_text_generation() {
524 let files = vec![GeneratedFile {
525 path: "test.rs".to_string(),
526 content: "fn main() {}".to_string(),
527 language: "rust".to_string(),
528 }];
529 let validation = ValidationResult::default();
530 let conflicts = vec![];
531 let stats = GenerationStats::default();
532
533 let result = GenerationResult::new(files, validation, conflicts, stats);
534 let text = ReportGenerator::generate_text(&result);
535
536 assert!(text.contains("Code Generation Report"));
537 assert!(text.contains("SUMMARY"));
538 assert!(text.contains("FILE STATISTICS"));
539 assert!(text.contains("VALIDATION RESULTS"));
540 }
541
542 #[test]
543 fn test_report_json_generation() {
544 let files = vec![GeneratedFile {
545 path: "test.rs".to_string(),
546 content: "fn main() {}".to_string(),
547 language: "rust".to_string(),
548 }];
549 let validation = ValidationResult::default();
550 let conflicts = vec![];
551 let stats = GenerationStats::default();
552
553 let result = GenerationResult::new(files, validation, conflicts, stats);
554 let json = ReportGenerator::generate_json(&result);
555
556 assert!(json.is_ok());
557 let json_str = json.unwrap();
558 assert!(json_str.contains("\"title\""));
559 assert!(json_str.contains("\"timestamp\""));
560 }
561}