ruvector_scipix/cli/commands/
doctor.rs

1//! Doctor command for environment analysis and configuration optimization
2//!
3//! Analyzes the system environment and provides recommendations for optimal
4//! SciPix configuration based on available hardware and software capabilities.
5
6use anyhow::Result;
7use clap::Args;
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11/// Arguments for the doctor command
12#[derive(Args, Debug, Clone)]
13pub struct DoctorArgs {
14    /// Run in fix mode to automatically apply recommendations
15    #[arg(long, help = "Automatically apply safe fixes")]
16    pub fix: bool,
17
18    /// Output detailed diagnostic information
19    #[arg(long, short, help = "Show detailed diagnostic information")]
20    pub verbose: bool,
21
22    /// Output results as JSON
23    #[arg(long, help = "Output results as JSON")]
24    pub json: bool,
25
26    /// Check only specific category (cpu, memory, config, deps, all)
27    #[arg(long, default_value = "all", help = "Category to check")]
28    pub check: CheckCategory,
29
30    /// Path to configuration file to validate
31    #[arg(long, help = "Path to configuration file to validate")]
32    pub config_path: Option<PathBuf>,
33}
34
35#[derive(Debug, Clone, Copy, clap::ValueEnum, Default)]
36pub enum CheckCategory {
37    #[default]
38    All,
39    Cpu,
40    Memory,
41    Config,
42    Deps,
43    Network,
44}
45
46/// Status of a diagnostic check
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub enum CheckStatus {
49    Pass,
50    Warning,
51    Fail,
52    Info,
53}
54
55impl std::fmt::Display for CheckStatus {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            CheckStatus::Pass => write!(f, "āœ“"),
59            CheckStatus::Warning => write!(f, "⚠"),
60            CheckStatus::Fail => write!(f, "āœ—"),
61            CheckStatus::Info => write!(f, "ℹ"),
62        }
63    }
64}
65
66/// A single diagnostic check result
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct DiagnosticCheck {
69    pub name: String,
70    pub category: String,
71    pub status: CheckStatus,
72    pub message: String,
73    pub recommendation: Option<String>,
74    pub auto_fixable: bool,
75}
76
77/// Complete diagnostic report
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct DiagnosticReport {
80    pub timestamp: String,
81    pub system_info: SystemInfo,
82    pub checks: Vec<DiagnosticCheck>,
83    pub recommendations: Vec<String>,
84    pub optimal_config: OptimalConfig,
85}
86
87/// System information gathered during diagnosis
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct SystemInfo {
90    pub os: String,
91    pub arch: String,
92    pub cpu_count: usize,
93    pub cpu_brand: String,
94    pub total_memory_mb: u64,
95    pub available_memory_mb: u64,
96    pub simd_features: SimdFeatures,
97}
98
99/// SIMD feature detection results
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct SimdFeatures {
102    pub sse2: bool,
103    pub sse4_1: bool,
104    pub sse4_2: bool,
105    pub avx: bool,
106    pub avx2: bool,
107    pub avx512f: bool,
108    pub neon: bool,
109    pub best_available: String,
110}
111
112/// Optimal configuration recommendations
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct OptimalConfig {
115    pub batch_size: usize,
116    pub worker_threads: usize,
117    pub simd_backend: String,
118    pub memory_limit_mb: u64,
119    pub preprocessing_mode: String,
120    pub cache_enabled: bool,
121    pub cache_size_mb: u64,
122}
123
124/// Execute the doctor command
125pub async fn execute(args: DoctorArgs) -> Result<()> {
126    if !args.json {
127        println!("🩺 SciPix Doctor - Environment Analysis\n");
128        println!("═══════════════════════════════════════════════════════════\n");
129    }
130
131    let mut checks = Vec::new();
132
133    // Gather system information
134    let system_info = gather_system_info();
135
136    // Run checks based on category
137    match args.check {
138        CheckCategory::All => {
139            checks.extend(check_cpu(&system_info, args.verbose));
140            checks.extend(check_memory(&system_info, args.verbose));
141            checks.extend(check_dependencies(args.verbose));
142            checks.extend(check_config(&args.config_path, args.verbose));
143            checks.extend(check_network(args.verbose).await);
144        }
145        CheckCategory::Cpu => {
146            checks.extend(check_cpu(&system_info, args.verbose));
147        }
148        CheckCategory::Memory => {
149            checks.extend(check_memory(&system_info, args.verbose));
150        }
151        CheckCategory::Config => {
152            checks.extend(check_config(&args.config_path, args.verbose));
153        }
154        CheckCategory::Deps => {
155            checks.extend(check_dependencies(args.verbose));
156        }
157        CheckCategory::Network => {
158            checks.extend(check_network(args.verbose).await);
159        }
160    }
161
162    // Generate optimal configuration
163    let optimal_config = generate_optimal_config(&system_info);
164
165    // Collect recommendations
166    let recommendations: Vec<String> = checks
167        .iter()
168        .filter_map(|c| c.recommendation.clone())
169        .collect();
170
171    // Create report
172    let report = DiagnosticReport {
173        timestamp: chrono::Utc::now().to_rfc3339(),
174        system_info: system_info.clone(),
175        checks: checks.clone(),
176        recommendations: recommendations.clone(),
177        optimal_config: optimal_config.clone(),
178    };
179
180    if args.json {
181        println!("{}", serde_json::to_string_pretty(&report)?);
182        return Ok(());
183    }
184
185    // Print system info
186    print_system_info(&system_info);
187
188    // Print check results
189    print_check_results(&checks);
190
191    // Print recommendations
192    if !recommendations.is_empty() {
193        println!("\nšŸ“‹ Recommendations:");
194        println!("───────────────────────────────────────────────────────────");
195        for (i, rec) in recommendations.iter().enumerate() {
196            println!("  {}. {}", i + 1, rec);
197        }
198    }
199
200    // Print optimal configuration
201    print_optimal_config(&optimal_config);
202
203    // Apply fixes if requested
204    if args.fix {
205        apply_fixes(&checks).await?;
206    }
207
208    // Print summary
209    print_summary(&checks);
210
211    Ok(())
212}
213
214fn gather_system_info() -> SystemInfo {
215    let cpu_count = num_cpus::get();
216
217    // Get CPU brand string
218    let cpu_brand = get_cpu_brand();
219
220    // Get memory info
221    let (total_memory_mb, available_memory_mb) = get_memory_info();
222
223    // Detect SIMD features
224    let simd_features = detect_simd_features();
225
226    SystemInfo {
227        os: std::env::consts::OS.to_string(),
228        arch: std::env::consts::ARCH.to_string(),
229        cpu_count,
230        cpu_brand,
231        total_memory_mb,
232        available_memory_mb,
233        simd_features,
234    }
235}
236
237fn get_cpu_brand() -> String {
238    #[cfg(target_arch = "x86_64")]
239    {
240        if let Some(brand) = get_x86_cpu_brand() {
241            return brand;
242        }
243    }
244
245    // Fallback
246    format!("{} processor", std::env::consts::ARCH)
247}
248
249#[cfg(target_arch = "x86_64")]
250fn get_x86_cpu_brand() -> Option<String> {
251    // Try to read from /proc/cpuinfo on Linux
252    if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
253        for line in cpuinfo.lines() {
254            if line.starts_with("model name") {
255                if let Some(brand) = line.split(':').nth(1) {
256                    return Some(brand.trim().to_string());
257                }
258            }
259        }
260    }
261    None
262}
263
264#[cfg(not(target_arch = "x86_64"))]
265fn get_x86_cpu_brand() -> Option<String> {
266    None
267}
268
269fn get_memory_info() -> (u64, u64) {
270    // Try to read from /proc/meminfo on Linux
271    if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
272        let mut total = 0u64;
273        let mut available = 0u64;
274
275        for line in meminfo.lines() {
276            if line.starts_with("MemTotal:") {
277                if let Some(kb) = parse_meminfo_value(line) {
278                    total = kb / 1024; // Convert to MB
279                }
280            } else if line.starts_with("MemAvailable:") {
281                if let Some(kb) = parse_meminfo_value(line) {
282                    available = kb / 1024; // Convert to MB
283                }
284            }
285        }
286
287        if total > 0 {
288            return (total, available);
289        }
290    }
291
292    // Fallback values
293    (8192, 4096)
294}
295
296fn parse_meminfo_value(line: &str) -> Option<u64> {
297    line.split_whitespace()
298        .nth(1)
299        .and_then(|s| s.parse().ok())
300}
301
302fn detect_simd_features() -> SimdFeatures {
303    let mut features = SimdFeatures {
304        sse2: false,
305        sse4_1: false,
306        sse4_2: false,
307        avx: false,
308        avx2: false,
309        avx512f: false,
310        neon: false,
311        best_available: "scalar".to_string(),
312    };
313
314    #[cfg(target_arch = "x86_64")]
315    {
316        features.sse2 = is_x86_feature_detected!("sse2");
317        features.sse4_1 = is_x86_feature_detected!("sse4.1");
318        features.sse4_2 = is_x86_feature_detected!("sse4.2");
319        features.avx = is_x86_feature_detected!("avx");
320        features.avx2 = is_x86_feature_detected!("avx2");
321        features.avx512f = is_x86_feature_detected!("avx512f");
322
323        if features.avx512f {
324            features.best_available = "AVX-512".to_string();
325        } else if features.avx2 {
326            features.best_available = "AVX2".to_string();
327        } else if features.avx {
328            features.best_available = "AVX".to_string();
329        } else if features.sse4_2 {
330            features.best_available = "SSE4.2".to_string();
331        } else if features.sse2 {
332            features.best_available = "SSE2".to_string();
333        }
334    }
335
336    #[cfg(target_arch = "aarch64")]
337    {
338        features.neon = true; // NEON is always available on AArch64
339        features.best_available = "NEON".to_string();
340    }
341
342    features
343}
344
345fn check_cpu(system_info: &SystemInfo, verbose: bool) -> Vec<DiagnosticCheck> {
346    let mut checks = Vec::new();
347
348    // CPU count check
349    let cpu_status = if system_info.cpu_count >= 8 {
350        CheckStatus::Pass
351    } else if system_info.cpu_count >= 4 {
352        CheckStatus::Warning
353    } else {
354        CheckStatus::Fail
355    };
356
357    checks.push(DiagnosticCheck {
358        name: "CPU Cores".to_string(),
359        category: "CPU".to_string(),
360        status: cpu_status,
361        message: format!("{} cores detected", system_info.cpu_count),
362        recommendation: if system_info.cpu_count < 4 {
363            Some("Consider running on a machine with more CPU cores for better batch processing".to_string())
364        } else {
365            None
366        },
367        auto_fixable: false,
368    });
369
370    // SIMD check
371    let simd_status = match system_info.simd_features.best_available.as_str() {
372        "AVX-512" | "AVX2" => CheckStatus::Pass,
373        "AVX" | "SSE4.2" | "NEON" => CheckStatus::Warning,
374        _ => CheckStatus::Fail,
375    };
376
377    checks.push(DiagnosticCheck {
378        name: "SIMD Support".to_string(),
379        category: "CPU".to_string(),
380        status: simd_status,
381        message: format!(
382            "Best SIMD: {} (SSE2: {}, AVX: {}, AVX2: {}, AVX-512: {})",
383            system_info.simd_features.best_available,
384            if system_info.simd_features.sse2 { "āœ“" } else { "āœ—" },
385            if system_info.simd_features.avx { "āœ“" } else { "āœ—" },
386            if system_info.simd_features.avx2 { "āœ“" } else { "āœ—" },
387            if system_info.simd_features.avx512f { "āœ“" } else { "āœ—" },
388        ),
389        recommendation: if simd_status == CheckStatus::Fail {
390            Some("Upgrade to a CPU with AVX2 support for 4x faster preprocessing".to_string())
391        } else {
392            None
393        },
394        auto_fixable: false,
395    });
396
397    if verbose {
398        checks.push(DiagnosticCheck {
399            name: "CPU Brand".to_string(),
400            category: "CPU".to_string(),
401            status: CheckStatus::Info,
402            message: system_info.cpu_brand.clone(),
403            recommendation: None,
404            auto_fixable: false,
405        });
406    }
407
408    checks
409}
410
411fn check_memory(system_info: &SystemInfo, verbose: bool) -> Vec<DiagnosticCheck> {
412    let mut checks = Vec::new();
413
414    // Total memory check
415    let mem_status = if system_info.total_memory_mb >= 16384 {
416        CheckStatus::Pass
417    } else if system_info.total_memory_mb >= 8192 {
418        CheckStatus::Warning
419    } else {
420        CheckStatus::Fail
421    };
422
423    checks.push(DiagnosticCheck {
424        name: "Total Memory".to_string(),
425        category: "Memory".to_string(),
426        status: mem_status,
427        message: format!("{} MB total", system_info.total_memory_mb),
428        recommendation: if system_info.total_memory_mb < 8192 {
429            Some("Consider upgrading to at least 8GB RAM for optimal batch processing".to_string())
430        } else {
431            None
432        },
433        auto_fixable: false,
434    });
435
436    // Available memory check
437    let avail_ratio = system_info.available_memory_mb as f64 / system_info.total_memory_mb as f64;
438    let avail_status = if avail_ratio >= 0.5 {
439        CheckStatus::Pass
440    } else if avail_ratio >= 0.25 {
441        CheckStatus::Warning
442    } else {
443        CheckStatus::Fail
444    };
445
446    checks.push(DiagnosticCheck {
447        name: "Available Memory".to_string(),
448        category: "Memory".to_string(),
449        status: avail_status,
450        message: format!(
451            "{} MB available ({:.1}%)",
452            system_info.available_memory_mb,
453            avail_ratio * 100.0
454        ),
455        recommendation: if avail_status == CheckStatus::Fail {
456            Some("Close some applications to free up memory before batch processing".to_string())
457        } else {
458            None
459        },
460        auto_fixable: false,
461    });
462
463    if verbose {
464        // Memory per core
465        let mem_per_core = system_info.total_memory_mb / system_info.cpu_count as u64;
466        checks.push(DiagnosticCheck {
467            name: "Memory per Core".to_string(),
468            category: "Memory".to_string(),
469            status: CheckStatus::Info,
470            message: format!("{} MB/core", mem_per_core),
471            recommendation: None,
472            auto_fixable: false,
473        });
474    }
475
476    checks
477}
478
479fn check_dependencies(verbose: bool) -> Vec<DiagnosticCheck> {
480    let mut checks = Vec::new();
481
482    // Check for ONNX Runtime
483    let onnx_status = check_onnx_runtime();
484    checks.push(DiagnosticCheck {
485        name: "ONNX Runtime".to_string(),
486        category: "Dependencies".to_string(),
487        status: if onnx_status.0 { CheckStatus::Pass } else { CheckStatus::Warning },
488        message: onnx_status.1.clone(),
489        recommendation: if !onnx_status.0 {
490            Some("Install ONNX Runtime for neural network acceleration: https://onnxruntime.ai/".to_string())
491        } else {
492            None
493        },
494        auto_fixable: false,
495    });
496
497    // Check for image processing libraries
498    checks.push(DiagnosticCheck {
499        name: "Image Processing".to_string(),
500        category: "Dependencies".to_string(),
501        status: CheckStatus::Pass,
502        message: "image crate available (built-in)".to_string(),
503        recommendation: None,
504        auto_fixable: false,
505    });
506
507    // Check for OpenSSL (for HTTPS)
508    let openssl_available = std::process::Command::new("openssl")
509        .arg("version")
510        .output()
511        .map(|o| o.status.success())
512        .unwrap_or(false);
513
514    checks.push(DiagnosticCheck {
515        name: "OpenSSL".to_string(),
516        category: "Dependencies".to_string(),
517        status: if openssl_available { CheckStatus::Pass } else { CheckStatus::Warning },
518        message: if openssl_available {
519            "OpenSSL available for HTTPS".to_string()
520        } else {
521            "OpenSSL not found".to_string()
522        },
523        recommendation: if !openssl_available {
524            Some("Install OpenSSL for secure API communication".to_string())
525        } else {
526            None
527        },
528        auto_fixable: false,
529    });
530
531    if verbose {
532        // Check Rust version
533        if let Ok(output) = std::process::Command::new("rustc").arg("--version").output() {
534            let version = String::from_utf8_lossy(&output.stdout);
535            checks.push(DiagnosticCheck {
536                name: "Rust Compiler".to_string(),
537                category: "Dependencies".to_string(),
538                status: CheckStatus::Info,
539                message: version.trim().to_string(),
540                recommendation: None,
541                auto_fixable: false,
542            });
543        }
544    }
545
546    checks
547}
548
549fn check_onnx_runtime() -> (bool, String) {
550    // Check for ONNX runtime shared library
551    let lib_paths = [
552        "/usr/lib/libonnxruntime.so",
553        "/usr/local/lib/libonnxruntime.so",
554        "/opt/onnxruntime/lib/libonnxruntime.so",
555    ];
556
557    for path in &lib_paths {
558        if std::path::Path::new(path).exists() {
559            return (true, format!("Found at {}", path));
560        }
561    }
562
563    // Check via environment variable
564    if std::env::var("ORT_DYLIB_PATH").is_ok() {
565        return (true, "Configured via ORT_DYLIB_PATH".to_string());
566    }
567
568    (false, "Not found (optional for ONNX acceleration)".to_string())
569}
570
571fn check_config(config_path: &Option<PathBuf>, verbose: bool) -> Vec<DiagnosticCheck> {
572    let mut checks = Vec::new();
573
574    // Check for config file
575    let config_locations = [
576        config_path.clone(),
577        Some(PathBuf::from("scipix.toml")),
578        Some(PathBuf::from("config/scipix.toml")),
579        dirs::config_dir().map(|p| p.join("scipix/config.toml")),
580    ];
581
582    let mut found_config = false;
583    for loc in config_locations.iter().flatten() {
584        if loc.exists() {
585            checks.push(DiagnosticCheck {
586                name: "Configuration File".to_string(),
587                category: "Config".to_string(),
588                status: CheckStatus::Pass,
589                message: format!("Found at {}", loc.display()),
590                recommendation: None,
591                auto_fixable: false,
592            });
593            found_config = true;
594
595            // Validate config content
596            if let Ok(content) = std::fs::read_to_string(loc) {
597                if content.contains("[api]") || content.contains("[processing]") {
598                    checks.push(DiagnosticCheck {
599                        name: "Config Validity".to_string(),
600                        category: "Config".to_string(),
601                        status: CheckStatus::Pass,
602                        message: "Configuration file is valid".to_string(),
603                        recommendation: None,
604                        auto_fixable: false,
605                    });
606                }
607            }
608            break;
609        }
610    }
611
612    if !found_config {
613        checks.push(DiagnosticCheck {
614            name: "Configuration File".to_string(),
615            category: "Config".to_string(),
616            status: CheckStatus::Info,
617            message: "No configuration file found (using defaults)".to_string(),
618            recommendation: Some("Create a scipix.toml for custom settings".to_string()),
619            auto_fixable: true,
620        });
621    }
622
623    // Check environment variables
624    let env_vars = [
625        ("SCIPIX_API_KEY", "API authentication"),
626        ("SCIPIX_MODEL_PATH", "Custom model path"),
627        ("SCIPIX_CACHE_DIR", "Cache directory"),
628    ];
629
630    for (var, desc) in &env_vars {
631        let status = if std::env::var(var).is_ok() {
632            CheckStatus::Pass
633        } else {
634            CheckStatus::Info
635        };
636
637        if verbose || status == CheckStatus::Pass {
638            checks.push(DiagnosticCheck {
639                name: format!("Env: {}", var),
640                category: "Config".to_string(),
641                status,
642                message: if status == CheckStatus::Pass {
643                    format!("{} configured", desc)
644                } else {
645                    format!("{} not set (optional)", desc)
646                },
647                recommendation: None,
648                auto_fixable: false,
649            });
650        }
651    }
652
653    checks
654}
655
656async fn check_network(verbose: bool) -> Vec<DiagnosticCheck> {
657    let mut checks = Vec::new();
658
659    // Check localhost binding
660    let localhost_available = tokio::net::TcpListener::bind("127.0.0.1:0")
661        .await
662        .is_ok();
663
664    checks.push(DiagnosticCheck {
665        name: "Localhost Binding".to_string(),
666        category: "Network".to_string(),
667        status: if localhost_available { CheckStatus::Pass } else { CheckStatus::Fail },
668        message: if localhost_available {
669            "Can bind to localhost".to_string()
670        } else {
671            "Cannot bind to localhost".to_string()
672        },
673        recommendation: if !localhost_available {
674            Some("Check firewall settings and port availability".to_string())
675        } else {
676            None
677        },
678        auto_fixable: false,
679    });
680
681    // Check common ports
682    let ports_to_check = [(8080, "API server"), (3000, "Alternative API")];
683
684    for (port, desc) in &ports_to_check {
685        let available = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port))
686            .await
687            .is_ok();
688
689        if verbose || !available {
690            checks.push(DiagnosticCheck {
691                name: format!("Port {}", port),
692                category: "Network".to_string(),
693                status: if available { CheckStatus::Pass } else { CheckStatus::Warning },
694                message: if available {
695                    format!("Port {} ({}) available", port, desc)
696                } else {
697                    format!("Port {} ({}) in use", port, desc)
698                },
699                recommendation: if !available {
700                    Some(format!("Free port {} or use --port to specify alternative", port))
701                } else {
702                    None
703                },
704                auto_fixable: false,
705            });
706        }
707    }
708
709    checks
710}
711
712fn generate_optimal_config(system_info: &SystemInfo) -> OptimalConfig {
713    // Calculate optimal batch size based on memory
714    let batch_size = if system_info.available_memory_mb >= 8192 {
715        32
716    } else if system_info.available_memory_mb >= 4096 {
717        16
718    } else if system_info.available_memory_mb >= 2048 {
719        8
720    } else {
721        4
722    };
723
724    // Calculate worker threads (leave some headroom)
725    let worker_threads = (system_info.cpu_count as f64 * 0.75).ceil() as usize;
726    let worker_threads = worker_threads.max(2);
727
728    // Determine SIMD backend
729    let simd_backend = system_info.simd_features.best_available.clone();
730
731    // Memory limit (use 60% of available)
732    let memory_limit_mb = (system_info.available_memory_mb as f64 * 0.6) as u64;
733
734    // Preprocessing mode based on SIMD
735    let preprocessing_mode = if system_info.simd_features.avx2 || system_info.simd_features.neon {
736        "simd_optimized".to_string()
737    } else if system_info.simd_features.sse4_2 {
738        "simd_basic".to_string()
739    } else {
740        "scalar".to_string()
741    };
742
743    // Cache settings
744    let cache_enabled = system_info.available_memory_mb >= 2048;
745    let cache_size_mb = if cache_enabled {
746        (system_info.available_memory_mb as f64 * 0.1) as u64
747    } else {
748        0
749    };
750
751    OptimalConfig {
752        batch_size,
753        worker_threads,
754        simd_backend,
755        memory_limit_mb,
756        preprocessing_mode,
757        cache_enabled,
758        cache_size_mb,
759    }
760}
761
762fn print_system_info(info: &SystemInfo) {
763    println!("šŸ“Š System Information:");
764    println!("───────────────────────────────────────────────────────────");
765    println!("  OS:           {} ({})", info.os, info.arch);
766    println!("  CPU:          {}", info.cpu_brand);
767    println!("  Cores:        {}", info.cpu_count);
768    println!("  Memory:       {} MB total, {} MB available",
769             info.total_memory_mb, info.available_memory_mb);
770    println!("  Best SIMD:    {}", info.simd_features.best_available);
771    println!();
772}
773
774fn print_check_results(checks: &[DiagnosticCheck]) {
775    println!("šŸ” Diagnostic Checks:");
776    println!("───────────────────────────────────────────────────────────");
777
778    let mut current_category = String::new();
779    for check in checks {
780        if check.category != current_category {
781            if !current_category.is_empty() {
782                println!();
783            }
784            println!("  [{}]", check.category);
785            current_category = check.category.clone();
786        }
787
788        let status_color = match check.status {
789            CheckStatus::Pass => "\x1b[32m",    // Green
790            CheckStatus::Warning => "\x1b[33m", // Yellow
791            CheckStatus::Fail => "\x1b[31m",    // Red
792            CheckStatus::Info => "\x1b[36m",    // Cyan
793        };
794
795        println!(
796            "    {}{}\x1b[0m {} - {}",
797            status_color, check.status, check.name, check.message
798        );
799    }
800    println!();
801}
802
803fn print_optimal_config(config: &OptimalConfig) {
804    println!("\nāš™ļø  Optimal Configuration:");
805    println!("───────────────────────────────────────────────────────────");
806    println!("  batch_size:        {}", config.batch_size);
807    println!("  worker_threads:    {}", config.worker_threads);
808    println!("  simd_backend:      {}", config.simd_backend);
809    println!("  memory_limit:      {} MB", config.memory_limit_mb);
810    println!("  preprocessing:     {}", config.preprocessing_mode);
811    println!("  cache_enabled:     {}", config.cache_enabled);
812    if config.cache_enabled {
813        println!("  cache_size:        {} MB", config.cache_size_mb);
814    }
815
816    println!("\n  šŸ“ Example configuration (scipix.toml):");
817    println!("  ─────────────────────────────────────────");
818    println!("  [processing]");
819    println!("  batch_size = {}", config.batch_size);
820    println!("  worker_threads = {}", config.worker_threads);
821    println!("  simd_backend = \"{}\"", config.simd_backend);
822    println!("  memory_limit_mb = {}", config.memory_limit_mb);
823    println!();
824    println!("  [cache]");
825    println!("  enabled = {}", config.cache_enabled);
826    println!("  size_mb = {}", config.cache_size_mb);
827}
828
829fn print_summary(checks: &[DiagnosticCheck]) {
830    let pass_count = checks.iter().filter(|c| c.status == CheckStatus::Pass).count();
831    let warn_count = checks.iter().filter(|c| c.status == CheckStatus::Warning).count();
832    let fail_count = checks.iter().filter(|c| c.status == CheckStatus::Fail).count();
833
834    println!("\n═══════════════════════════════════════════════════════════");
835    println!(
836        "šŸ“‹ Summary: {} passed, {} warnings, {} failed",
837        pass_count, warn_count, fail_count
838    );
839
840    if fail_count > 0 {
841        println!("\nāš ļø  Some checks failed. Review recommendations above.");
842    } else if warn_count > 0 {
843        println!("\nāœ“ System is functional with some areas for improvement.");
844    } else {
845        println!("\nāœ… System is optimally configured for SciPix!");
846    }
847}
848
849async fn apply_fixes(checks: &[DiagnosticCheck]) -> Result<()> {
850    println!("\nšŸ”§ Applying automatic fixes...");
851    println!("───────────────────────────────────────────────────────────");
852
853    let fixable: Vec<_> = checks.iter().filter(|c| c.auto_fixable).collect();
854
855    if fixable.is_empty() {
856        println!("  No automatic fixes available.");
857        return Ok(());
858    }
859
860    for check in fixable {
861        println!("  Fixing: {}", check.name);
862
863        if check.name == "Configuration File" {
864            // Create default config file
865            let config_content = r#"# SciPix Configuration
866# Generated by scipix doctor --fix
867
868[processing]
869batch_size = 16
870worker_threads = 4
871simd_backend = "auto"
872memory_limit_mb = 4096
873
874[cache]
875enabled = true
876size_mb = 256
877
878[api]
879host = "127.0.0.1"
880port = 8080
881timeout_seconds = 30
882
883[logging]
884level = "info"
885format = "pretty"
886"#;
887
888            // Create config directory if needed
889            let config_path = PathBuf::from("config");
890            if !config_path.exists() {
891                std::fs::create_dir_all(&config_path)?;
892            }
893
894            let config_file = config_path.join("scipix.toml");
895            std::fs::write(&config_file, config_content)?;
896            println!("    āœ“ Created {}", config_file.display());
897        }
898    }
899
900    Ok(())
901}