syncable_cli/analyzer/
display.rs

1//! # Display Module
2//! 
3//! Provides improved CLI output formatting with matrix/dashboard views for better readability
4//! and easier parsing by both humans and LLMs.
5
6use crate::analyzer::{
7    MonorepoAnalysis, ProjectCategory, ArchitecturePattern,
8    DetectedTechnology, TechnologyCategory, LibraryType,
9    DockerAnalysis, OrchestrationPattern,
10};
11use colored::*;
12
13/// Content line for measuring and drawing
14#[derive(Debug, Clone)]
15struct ContentLine {
16    label: String,
17    value: String,
18    label_colored: bool,
19}
20
21impl ContentLine {
22    fn new(label: &str, value: &str, label_colored: bool) -> Self {
23        Self {
24            label: label.to_string(),
25            value: value.to_string(),
26            label_colored,
27        }
28    }
29    
30    
31    fn separator() -> Self {
32        Self {
33            label: "SEPARATOR".to_string(),
34            value: String::new(),
35            label_colored: false,
36        }
37    }
38    
39
40}
41
42/// Box drawer that pre-calculates optimal dimensions
43pub struct BoxDrawer {
44    title: String,
45    lines: Vec<ContentLine>,
46    min_width: usize,
47    max_width: usize,
48}
49
50impl BoxDrawer {
51    pub fn new(title: &str) -> Self {
52        Self {
53            title: title.to_string(),
54            lines: Vec::new(),
55            min_width: 60,
56            max_width: 120, // Reduced from 150 for better terminal compatibility
57        }
58    }
59    
60    pub fn add_line(&mut self, label: &str, value: &str, label_colored: bool) {
61        self.lines.push(ContentLine::new(label, value, label_colored));
62    }
63    
64    pub fn add_value_only(&mut self, value: &str) {
65        self.lines.push(ContentLine::new("", value, false));
66    }
67    
68    pub fn add_separator(&mut self) {
69        self.lines.push(ContentLine::separator());
70    }
71    
72    /// Calculate optimal box width based on content
73    fn calculate_optimal_width(&self) -> usize {
74        let title_width = visual_width(&self.title) + 6; // "┌─ " + title + " " + extra padding
75        let mut max_content_width = 0;
76        
77        // Calculate the actual rendered width for each line
78        for line in &self.lines {
79            if line.label == "SEPARATOR" {
80                continue;
81            }
82            
83            let rendered_width = self.calculate_rendered_line_width(line);
84            max_content_width = max_content_width.max(rendered_width);
85        }
86        
87        // Add reasonable buffer for content
88        let content_width_with_buffer = max_content_width + 4; // More buffer for safety
89        
90        // Box needs padding: "│ " + content + " │" = content + 4
91        let needed_width = content_width_with_buffer + 4;
92        
93        // Use the maximum of title width and content width
94        let optimal_width = title_width.max(needed_width).max(self.min_width);
95        optimal_width.min(self.max_width)
96    }
97    
98    /// Calculate the actual rendered width of a line as it will appear
99    fn calculate_rendered_line_width(&self, line: &ContentLine) -> usize {
100        let label_width = visual_width(&line.label);
101        let value_width = visual_width(&line.value);
102        
103        if !line.label.is_empty() && !line.value.is_empty() {
104            // Label + value: need space between them
105            // For colored labels, ensure minimum spacing
106            let min_label_space = if line.label_colored { 25 } else { label_width };
107            min_label_space + 2 + value_width // 2 spaces minimum between label and value
108        } else if !line.value.is_empty() {
109            // Value only
110            value_width
111        } else if !line.label.is_empty() {
112            // Label only
113            label_width
114        } else {
115            // Empty line
116            0
117        }
118    }
119    
120    /// Draw the complete box
121    pub fn draw(&self) -> String {
122        let box_width = self.calculate_optimal_width();
123        let content_width = box_width - 4; // Available space for content
124        
125        let mut output = Vec::new();
126        
127        // Top border
128        output.push(self.draw_top(box_width));
129        
130        // Content lines
131        for line in &self.lines {
132            if line.label == "SEPARATOR" {
133                output.push(self.draw_separator(box_width));
134            } else if line.label.is_empty() && line.value.is_empty() {
135                output.push(self.draw_empty_line(box_width));
136            } else {
137                output.push(self.draw_content_line(line, content_width));
138            }
139        }
140        
141        // Bottom border
142        output.push(self.draw_bottom(box_width));
143        
144        output.join("\n")
145    }
146    
147    fn draw_top(&self, width: usize) -> String {
148        let title_colored = self.title.bright_cyan();
149        let title_len = visual_width(&self.title);
150        
151        // "┌─ " + title + " " + remaining dashes + "┐"
152        let prefix_len = 3; // "┌─ "
153        let suffix_len = 1; // "┐"
154        let title_space = 1; // space after title
155        
156        let remaining_space = width - prefix_len - title_len - title_space - suffix_len;
157        
158        format!("┌─ {} {}┐", 
159            title_colored,
160            "─".repeat(remaining_space)
161        )
162    }
163    
164    fn draw_bottom(&self, width: usize) -> String {
165        format!("└{}┘", "─".repeat(width - 2))
166    }
167    
168    fn draw_separator(&self, width: usize) -> String {
169        format!("│ {} │", "─".repeat(width - 4).dimmed())
170    }
171    
172    fn draw_empty_line(&self, width: usize) -> String {
173        format!("│ {} │", " ".repeat(width - 4))
174    }
175    
176    fn draw_content_line(&self, line: &ContentLine, content_width: usize) -> String {
177        // Format the label with color if needed
178        let formatted_label = if line.label_colored && !line.label.is_empty() {
179            line.label.bright_white().to_string()
180        } else {
181            line.label.clone()
182        };
183        
184        // Calculate actual display widths (use original label for width)
185        let label_display_width = visual_width(&line.label);
186        let value_display_width = visual_width(&line.value);
187        
188        // Build the content
189        let content = if !line.label.is_empty() && !line.value.is_empty() {
190            // Both label and value - ensure proper spacing
191            let min_label_space = if line.label_colored { 25 } else { label_display_width };
192            let label_padding = min_label_space.saturating_sub(label_display_width);
193            let remaining_space = content_width.saturating_sub(min_label_space + 2); // 2 for spacing
194            
195            if value_display_width <= remaining_space {
196                // Value fits - right align it
197                let value_padding = remaining_space.saturating_sub(value_display_width);
198                format!("{}{:<width$}  {}{}", 
199                    formatted_label, 
200                    "",
201                    " ".repeat(value_padding),
202                    line.value,
203                    width = label_padding
204                )
205            } else {
206                // Value too long - truncate it
207                let truncated_value = truncate_to_width(&line.value, remaining_space.saturating_sub(3));
208                format!("{}{:<width$}  {}", 
209                    formatted_label, 
210                    "",
211                    truncated_value,
212                    width = label_padding
213                )
214            }
215        } else if !line.value.is_empty() {
216            // Value only - left align
217            if value_display_width <= content_width {
218                format!("{:<width$}", line.value, width = content_width)
219            } else {
220                truncate_to_width(&line.value, content_width)
221            }
222        } else if !line.label.is_empty() {
223            // Label only - left align
224            if label_display_width <= content_width {
225                format!("{:<width$}", formatted_label, width = content_width)
226            } else {
227                truncate_to_width(&formatted_label, content_width)
228            }
229        } else {
230            // Empty line
231            " ".repeat(content_width)
232        };
233        
234        // Ensure final content is exactly the right width
235        let actual_width = visual_width(&content);
236        let final_content = if actual_width < content_width {
237            format!("{}{}", content, " ".repeat(content_width - actual_width))
238        } else if actual_width > content_width {
239            truncate_to_width(&content, content_width)
240        } else {
241            content
242        };
243        
244        format!("│ {} │", final_content)
245    }
246}
247
248/// Calculate visual width of a string, handling ANSI color codes
249fn visual_width(s: &str) -> usize {
250    let mut width = 0;
251    let mut chars = s.chars().peekable();
252    
253    while let Some(ch) = chars.next() {
254        if ch == '\x1b' {
255            // Skip ANSI escape sequence
256            if chars.peek() == Some(&'[') {
257                chars.next(); // consume '['
258                while let Some(c) = chars.next() {
259                    if c.is_ascii_alphabetic() {
260                        break; // End of escape sequence
261                    }
262                }
263            }
264        } else {
265            // Simple width calculation for common cases
266            // Most characters are width 1, some are width 0 or 2
267            width += char_width(ch);
268        }
269    }
270    
271    width
272}
273
274/// Simple character width calculation without external dependencies
275fn char_width(ch: char) -> usize {
276    match ch {
277        // Control characters have width 0
278        '\u{0000}'..='\u{001F}' | '\u{007F}' => 0,
279        // Combining marks have width 0
280        '\u{0300}'..='\u{036F}' => 0,
281        // Emoji and symbols (width 2)
282        '\u{2600}'..='\u{26FF}' |    // Miscellaneous Symbols
283        '\u{2700}'..='\u{27BF}' |    // Dingbats
284        '\u{1F000}'..='\u{1F02F}' |  // Mahjong Tiles
285        '\u{1F030}'..='\u{1F09F}' |  // Domino Tiles
286        '\u{1F0A0}'..='\u{1F0FF}' |  // Playing Cards
287        '\u{1F100}'..='\u{1F1FF}' |  // Enclosed Alphanumeric Supplement
288        '\u{1F200}'..='\u{1F2FF}' |  // Enclosed Ideographic Supplement
289        '\u{1F300}'..='\u{1F5FF}' |  // Miscellaneous Symbols and Pictographs
290        '\u{1F600}'..='\u{1F64F}' |  // Emoticons
291        '\u{1F650}'..='\u{1F67F}' |  // Ornamental Dingbats
292        '\u{1F680}'..='\u{1F6FF}' |  // Transport and Map Symbols
293        '\u{1F700}'..='\u{1F77F}' |  // Alchemical Symbols
294        '\u{1F780}'..='\u{1F7FF}' |  // Geometric Shapes Extended
295        '\u{1F800}'..='\u{1F8FF}' |  // Supplemental Arrows-C
296        '\u{1F900}'..='\u{1F9FF}' |  // Supplemental Symbols and Pictographs
297        // Full-width characters (common CJK ranges)
298        '\u{1100}'..='\u{115F}' |  // Hangul Jamo
299        '\u{2E80}'..='\u{2EFF}' |  // CJK Radicals
300        '\u{2F00}'..='\u{2FDF}' |  // Kangxi Radicals
301        '\u{2FF0}'..='\u{2FFF}' |  // Ideographic Description
302        '\u{3000}'..='\u{303E}' |  // CJK Symbols and Punctuation
303        '\u{3041}'..='\u{3096}' |  // Hiragana
304        '\u{30A1}'..='\u{30FA}' |  // Katakana
305        '\u{3105}'..='\u{312D}' |  // Bopomofo
306        '\u{3131}'..='\u{318E}' |  // Hangul Compatibility Jamo
307        '\u{3190}'..='\u{31BA}' |  // Kanbun
308        '\u{31C0}'..='\u{31E3}' |  // CJK Strokes
309        '\u{31F0}'..='\u{31FF}' |  // Katakana Phonetic Extensions
310        '\u{3200}'..='\u{32FF}' |  // Enclosed CJK Letters and Months
311        '\u{3300}'..='\u{33FF}' |  // CJK Compatibility
312        '\u{3400}'..='\u{4DBF}' |  // CJK Extension A
313        '\u{4E00}'..='\u{9FFF}' |  // CJK Unified Ideographs
314        '\u{A000}'..='\u{A48C}' |  // Yi Syllables
315        '\u{A490}'..='\u{A4C6}' |  // Yi Radicals
316        '\u{AC00}'..='\u{D7AF}' |  // Hangul Syllables
317        '\u{F900}'..='\u{FAFF}' |  // CJK Compatibility Ideographs
318        '\u{FE10}'..='\u{FE19}' |  // Vertical Forms
319        '\u{FE30}'..='\u{FE6F}' |  // CJK Compatibility Forms
320        '\u{FF00}'..='\u{FF60}' |  // Fullwidth Forms
321        '\u{FFE0}'..='\u{FFE6}' => 2,
322        // Most other printable characters have width 1
323        _ => 1,
324    }
325}
326
327/// Truncate string to specified visual width, preserving color codes
328fn truncate_to_width(s: &str, max_width: usize) -> String {
329    let current_visual_width = visual_width(s);
330    if current_visual_width <= max_width {
331        return s.to_string();
332    }
333    
334    // For strings with ANSI codes, we need to be more careful
335    if s.contains('\x1b') {
336        // Simple approach: strip ANSI codes, truncate, then re-apply if needed
337        let stripped = strip_ansi_codes(s);
338        if visual_width(&stripped) <= max_width {
339            return s.to_string();
340        }
341        
342        // Truncate the stripped version
343        let mut result = String::new();
344        let mut width = 0;
345        for ch in stripped.chars() {
346            let ch_width = char_width(ch);
347            if width + ch_width > max_width.saturating_sub(3) {
348                result.push_str("...");
349                break;
350            }
351            result.push(ch);
352            width += ch_width;
353        }
354        return result;
355    }
356    
357    // No ANSI codes - simple truncation
358    let mut result = String::new();
359    let mut width = 0;
360    
361    for ch in s.chars() {
362        let ch_width = char_width(ch);
363        if width + ch_width > max_width.saturating_sub(3) {
364            result.push_str("...");
365            break;
366        }
367        result.push(ch);
368        width += ch_width;
369    }
370    
371    result
372}
373
374/// Strip ANSI escape codes from a string
375fn strip_ansi_codes(s: &str) -> String {
376    let mut result = String::new();
377    let mut chars = s.chars().peekable();
378    
379    while let Some(ch) = chars.next() {
380        if ch == '\x1b' {
381            // Skip ANSI escape sequence
382            if chars.peek() == Some(&'[') {
383                chars.next(); // consume '['
384                while let Some(c) = chars.next() {
385                    if c.is_ascii_alphabetic() {
386                        break; // End of escape sequence
387                    }
388                }
389            }
390        } else {
391            result.push(ch);
392        }
393    }
394    
395    result
396}
397
398/// Display mode for analysis output
399#[derive(Debug, Clone, Copy, PartialEq)]
400pub enum DisplayMode {
401    /// Compact matrix view (default)
402    Matrix,
403    /// Detailed vertical view (legacy)
404    Detailed,
405    /// Summary only
406    Summary,
407    /// JSON output
408    Json,
409}
410
411/// Main display function that routes to appropriate formatter
412pub fn display_analysis(analysis: &MonorepoAnalysis, mode: DisplayMode) {
413    match mode {
414        DisplayMode::Matrix => display_matrix_view(analysis),
415        DisplayMode::Detailed => display_detailed_view(analysis),
416        DisplayMode::Summary => display_summary_view(analysis),
417        DisplayMode::Json => display_json_view(analysis),
418    }
419}
420
421/// Main display function that returns a string instead of printing
422pub fn display_analysis_to_string(analysis: &MonorepoAnalysis, mode: DisplayMode) -> String {
423    match mode {
424        DisplayMode::Matrix => display_matrix_view_to_string(analysis),
425        DisplayMode::Detailed => display_detailed_view_to_string(analysis),
426        DisplayMode::Summary => display_summary_view_to_string(analysis),
427        DisplayMode::Json => display_json_view_to_string(analysis),
428    }
429}
430
431/// Combined function that both prints and returns a string
432pub fn display_analysis_with_return(analysis: &MonorepoAnalysis, mode: DisplayMode) -> String {
433    let output = display_analysis_to_string(analysis, mode);
434    print!("{}", output);
435    output
436}
437
438/// Display analysis in a compact matrix/dashboard format
439pub fn display_matrix_view(analysis: &MonorepoAnalysis) {
440    // Header
441    println!("\n{}", "═".repeat(100).bright_blue());
442    println!("{}", "📊 PROJECT ANALYSIS DASHBOARD".bright_white().bold());
443    println!("{}", "═".repeat(100).bright_blue());
444    
445    // Architecture Overview Box
446    display_architecture_box(analysis);
447    
448    // Technology Stack Box
449    display_technology_stack_box(analysis);
450    
451    // Projects Matrix
452    if analysis.projects.len() > 1 {
453        display_projects_matrix(analysis);
454    } else {
455        display_single_project_matrix(analysis);
456    }
457    
458    // Docker Infrastructure Overview
459    if analysis.projects.iter().any(|p| p.analysis.docker_analysis.is_some()) {
460        display_docker_overview_matrix(analysis);
461    }
462    
463    // Analysis Metrics Box
464    display_metrics_box(analysis);
465    
466    // Footer
467    println!("\n{}", "═".repeat(100).bright_blue());
468}
469
470/// Display analysis in a compact matrix/dashboard format - returns string
471pub fn display_matrix_view_to_string(analysis: &MonorepoAnalysis) -> String {
472    let mut output = String::new();
473    
474    // Header
475    output.push_str(&format!("\n{}\n", "═".repeat(100).bright_blue()));
476    output.push_str(&format!("{}\n", "📊 PROJECT ANALYSIS DASHBOARD".bright_white().bold()));
477    output.push_str(&format!("{}\n", "═".repeat(100).bright_blue()));
478    
479    // Architecture Overview Box
480    output.push_str(&display_architecture_box_to_string(analysis));
481    
482    // Technology Stack Box
483    output.push_str(&display_technology_stack_box_to_string(analysis));
484    
485    // Projects Matrix
486    if analysis.projects.len() > 1 {
487        output.push_str(&display_projects_matrix_to_string(analysis));
488    } else {
489        output.push_str(&display_single_project_matrix_to_string(analysis));
490    }
491    
492    // Docker Infrastructure Overview
493    if analysis.projects.iter().any(|p| p.analysis.docker_analysis.is_some()) {
494        output.push_str(&display_docker_overview_matrix_to_string(analysis));
495    }
496    
497    // Analysis Metrics Box
498    output.push_str(&display_metrics_box_to_string(analysis));
499    
500    // Footer
501    output.push_str(&format!("\n{}\n", "═".repeat(100).bright_blue()));
502    
503    output
504}
505
506/// Display architecture overview in a box
507fn display_architecture_box(analysis: &MonorepoAnalysis) {
508    let mut box_drawer = BoxDrawer::new("Architecture Overview");
509    
510    let arch_type = if analysis.is_monorepo {
511        format!("Monorepo ({} projects)", analysis.projects.len())
512    } else {
513        "Single Project".to_string()
514    };
515    
516    box_drawer.add_line("Type:", &arch_type.yellow(), true);
517    box_drawer.add_line("Pattern:", &format!("{:?}", analysis.technology_summary.architecture_pattern).green(), true);
518    
519    // Pattern description
520    let pattern_desc = match &analysis.technology_summary.architecture_pattern {
521        ArchitecturePattern::Monolithic => "Single, self-contained application",
522        ArchitecturePattern::Fullstack => "Full-stack app with frontend/backend separation",
523        ArchitecturePattern::Microservices => "Multiple independent microservices",
524        ArchitecturePattern::ApiFirst => "API-first architecture with service interfaces",
525        ArchitecturePattern::EventDriven => "Event-driven with decoupled components",
526        ArchitecturePattern::Mixed => "Mixed architecture patterns",
527    };
528    box_drawer.add_value_only(&pattern_desc.dimmed());
529    
530    println!("\n{}", box_drawer.draw());
531}
532
533/// Display architecture overview in a box - returns string
534fn display_architecture_box_to_string(analysis: &MonorepoAnalysis) -> String {
535    let mut box_drawer = BoxDrawer::new("Architecture Overview");
536    
537    let arch_type = if analysis.is_monorepo {
538        format!("Monorepo ({} projects)", analysis.projects.len())
539    } else {
540        "Single Project".to_string()
541    };
542    
543    box_drawer.add_line("Type:", &arch_type.yellow(), true);
544    box_drawer.add_line("Pattern:", &format!("{:?}", analysis.technology_summary.architecture_pattern).green(), true);
545    
546    // Pattern description
547    let pattern_desc = match &analysis.technology_summary.architecture_pattern {
548        ArchitecturePattern::Monolithic => "Single, self-contained application",
549        ArchitecturePattern::Fullstack => "Full-stack app with frontend/backend separation",
550        ArchitecturePattern::Microservices => "Multiple independent microservices",
551        ArchitecturePattern::ApiFirst => "API-first architecture with service interfaces",
552        ArchitecturePattern::EventDriven => "Event-driven with decoupled components",
553        ArchitecturePattern::Mixed => "Mixed architecture patterns",
554    };
555    box_drawer.add_value_only(&pattern_desc.dimmed());
556    
557    format!("\n{}", box_drawer.draw())
558}
559
560/// Display technology stack overview
561fn display_technology_stack_box(analysis: &MonorepoAnalysis) {
562    let mut box_drawer = BoxDrawer::new("Technology Stack");
563    
564    let mut has_content = false;
565    
566    // Languages
567    if !analysis.technology_summary.languages.is_empty() {
568        let languages = analysis.technology_summary.languages.join(", ");
569        box_drawer.add_line("Languages:", &languages.blue(), true);
570        has_content = true;
571    }
572    
573    // Frameworks
574    if !analysis.technology_summary.frameworks.is_empty() {
575        let frameworks = analysis.technology_summary.frameworks.join(", ");
576        box_drawer.add_line("Frameworks:", &frameworks.magenta(), true);
577        has_content = true;
578    }
579    
580    // Databases
581    if !analysis.technology_summary.databases.is_empty() {
582        let databases = analysis.technology_summary.databases.join(", ");
583        box_drawer.add_line("Databases:", &databases.cyan(), true);
584        has_content = true;
585    }
586    
587    if !has_content {
588        box_drawer.add_value_only("No technologies detected");
589    }
590    
591    println!("\n{}", box_drawer.draw());
592}
593
594/// Display technology stack overview - returns string
595fn display_technology_stack_box_to_string(analysis: &MonorepoAnalysis) -> String {
596    let mut box_drawer = BoxDrawer::new("Technology Stack");
597    
598    let mut has_content = false;
599    
600    // Languages
601    if !analysis.technology_summary.languages.is_empty() {
602        let languages = analysis.technology_summary.languages.join(", ");
603        box_drawer.add_line("Languages:", &languages.blue(), true);
604        has_content = true;
605    }
606    
607    // Frameworks
608    if !analysis.technology_summary.frameworks.is_empty() {
609        let frameworks = analysis.technology_summary.frameworks.join(", ");
610        box_drawer.add_line("Frameworks:", &frameworks.magenta(), true);
611        has_content = true;
612    }
613    
614    // Databases
615    if !analysis.technology_summary.databases.is_empty() {
616        let databases = analysis.technology_summary.databases.join(", ");
617        box_drawer.add_line("Databases:", &databases.cyan(), true);
618        has_content = true;
619    }
620    
621    if !has_content {
622        box_drawer.add_value_only("No technologies detected");
623    }
624    
625    format!("\n{}", box_drawer.draw())
626}
627
628/// Display projects in a matrix table format
629fn display_projects_matrix(analysis: &MonorepoAnalysis) {
630    let mut box_drawer = BoxDrawer::new("Projects Matrix");
631    
632    // Collect all data first to calculate optimal column widths
633    let mut project_data = Vec::new();
634    for project in &analysis.projects {
635        let name = project.name.clone(); // Remove emoji to avoid width calculation issues
636        let proj_type = format_project_category(&project.project_category);
637        
638        let languages = project.analysis.languages.iter()
639            .map(|l| l.name.clone())
640            .collect::<Vec<_>>()
641            .join(", ");
642        
643        let main_tech = get_main_technologies(&project.analysis.technologies);
644        
645        let ports = if project.analysis.ports.is_empty() {
646            "-".to_string()
647        } else {
648            project.analysis.ports.iter()
649                .map(|p| p.number.to_string())
650                .collect::<Vec<_>>()
651                .join(", ")
652        };
653        
654        let docker = if project.analysis.docker_analysis.is_some() {
655            "Yes"
656        } else {
657            "No"
658        };
659        
660        let deps_count = project.analysis.dependencies.len().to_string();
661        
662        project_data.push((name, proj_type.to_string(), languages, main_tech, ports, docker.to_string(), deps_count));
663    }
664    
665    // Calculate column widths based on content
666    let headers = vec!["Project", "Type", "Languages", "Main Tech", "Ports", "Docker", "Deps"];
667    let mut col_widths = headers.iter().map(|h| visual_width(h)).collect::<Vec<_>>();
668    
669    for (name, proj_type, languages, main_tech, ports, docker, deps_count) in &project_data {
670        col_widths[0] = col_widths[0].max(visual_width(name));
671        col_widths[1] = col_widths[1].max(visual_width(proj_type));
672        col_widths[2] = col_widths[2].max(visual_width(languages));
673        col_widths[3] = col_widths[3].max(visual_width(main_tech));
674        col_widths[4] = col_widths[4].max(visual_width(ports));
675        col_widths[5] = col_widths[5].max(visual_width(docker));
676        col_widths[6] = col_widths[6].max(visual_width(deps_count));
677    }
678    
679
680    // Create header row
681    let header_parts: Vec<String> = headers.iter().zip(&col_widths)
682        .map(|(h, &w)| format!("{:<width$}", h, width = w))
683        .collect();
684    let header_line = header_parts.join(" │ ");
685    box_drawer.add_value_only(&header_line);
686    
687    // Add separator
688    let separator_parts: Vec<String> = col_widths.iter()
689        .map(|&w| "─".repeat(w))
690        .collect();
691    let separator_line = separator_parts.join("─┼─");
692    box_drawer.add_value_only(&separator_line);
693    
694    // Add data rows
695    for (name, proj_type, languages, main_tech, ports, docker, deps_count) in project_data {
696        let row_parts = vec![
697            format!("{:<width$}", name, width = col_widths[0]),
698            format!("{:<width$}", proj_type, width = col_widths[1]),
699            format!("{:<width$}", languages, width = col_widths[2]),
700            format!("{:<width$}", main_tech, width = col_widths[3]),
701            format!("{:<width$}", ports, width = col_widths[4]),
702            format!("{:<width$}", docker, width = col_widths[5]),
703            format!("{:<width$}", deps_count, width = col_widths[6]),
704        ];
705        let row_line = row_parts.join(" │ ");
706        box_drawer.add_value_only(&row_line);
707    }
708    
709    println!("\n{}", box_drawer.draw());
710}
711
712/// Display projects in a matrix table format - returns string
713fn display_projects_matrix_to_string(analysis: &MonorepoAnalysis) -> String {
714    let mut box_drawer = BoxDrawer::new("Projects Matrix");
715    
716    // Simple implementation for the matrix view
717    for project in &analysis.projects {
718        let project_info = format!("{} ({})", project.name, format_project_category(&project.project_category));
719        box_drawer.add_value_only(&project_info);
720    }
721    
722    format!("\n{}", box_drawer.draw())
723}
724
725/// Display single project in matrix format
726fn display_single_project_matrix(analysis: &MonorepoAnalysis) {
727    if let Some(project) = analysis.projects.first() {
728        let mut box_drawer = BoxDrawer::new("Project Overview");
729        
730        // Basic info
731        box_drawer.add_line("Name:", &project.name.yellow(), true);
732        box_drawer.add_line("Type:", &format_project_category(&project.project_category).green(), true);
733        
734        // Languages 
735        if !project.analysis.languages.is_empty() {
736            let lang_info = project.analysis.languages.iter()
737                .map(|l| l.name.clone())
738                .collect::<Vec<_>>()
739                .join(", ");
740            box_drawer.add_line("Languages:", &lang_info.blue(), true);
741        }
742        
743        // Technologies by category (simplified for string version)
744        if !project.analysis.technologies.is_empty() {
745            let tech_names = project.analysis.technologies.iter()
746                .take(3)
747                .map(|t| t.name.clone())
748                .collect::<Vec<_>>()
749                .join(", ");
750            box_drawer.add_line("Technologies:", &tech_names.magenta(), true);
751        }
752        
753        // Key metrics
754        box_drawer.add_separator();
755        box_drawer.add_line("Key Metrics:", "", true);
756        
757        // Display metrics on two lines to fit properly
758        box_drawer.add_value_only(&format!("Entry Points: {} │ Exposed Ports: {} │ Env Variables: {}", 
759            project.analysis.entry_points.len(),
760            project.analysis.ports.len(),
761            project.analysis.environment_variables.len()
762        ).cyan());
763        
764        box_drawer.add_value_only(&format!("Build Scripts: {} │ Dependencies: {}", 
765            project.analysis.build_scripts.len(),
766            project.analysis.dependencies.len()
767        ).cyan());
768        
769        // Confidence score with progress bar
770        add_confidence_bar_to_drawer(project.analysis.analysis_metadata.confidence_score, &mut box_drawer);
771        
772        println!("\n{}", box_drawer.draw());
773    }
774}
775
776/// Display single project in matrix format - returns string
777fn display_single_project_matrix_to_string(analysis: &MonorepoAnalysis) -> String {
778    if let Some(project) = analysis.projects.first() {
779        let mut box_drawer = BoxDrawer::new("Project Overview");
780        
781        // Basic info
782        box_drawer.add_line("Name:", &project.name.yellow(), true);
783        box_drawer.add_line("Type:", &format_project_category(&project.project_category).green(), true);
784        
785        // Languages 
786        if !project.analysis.languages.is_empty() {
787            let lang_info = project.analysis.languages.iter()
788                .map(|l| l.name.clone())
789                .collect::<Vec<_>>()
790                .join(", ");
791            box_drawer.add_line("Languages:", &lang_info.blue(), true);
792        }
793        
794        // Key metrics
795        box_drawer.add_separator();
796        box_drawer.add_line("Key Metrics:", "", true);
797        
798        box_drawer.add_value_only(&format!("Entry Points: {} │ Exposed Ports: {} │ Env Variables: {}", 
799            project.analysis.entry_points.len(),
800            project.analysis.ports.len(),
801            project.analysis.environment_variables.len()
802        ).cyan());
803        
804        box_drawer.add_value_only(&format!("Build Scripts: {} │ Dependencies: {}", 
805            project.analysis.build_scripts.len(),
806            project.analysis.dependencies.len()
807        ).cyan());
808        
809        format!("\n{}", box_drawer.draw())
810    } else {
811        String::new()
812    }
813}
814
815/// Display Docker infrastructure overview in matrix format
816fn display_docker_overview_matrix(analysis: &MonorepoAnalysis) {
817    let mut box_drawer = BoxDrawer::new("Docker Infrastructure");
818    
819    let mut total_dockerfiles = 0;
820    let mut total_compose_files = 0;
821    let mut total_services = 0;
822    let mut orchestration_patterns = std::collections::HashSet::new();
823    
824    for project in &analysis.projects {
825        if let Some(docker) = &project.analysis.docker_analysis {
826            total_dockerfiles += docker.dockerfiles.len();
827            total_compose_files += docker.compose_files.len();
828            total_services += docker.services.len();
829            orchestration_patterns.insert(&docker.orchestration_pattern);
830        }
831    }
832    
833    box_drawer.add_line("Dockerfiles:", &total_dockerfiles.to_string().yellow(), true);
834    box_drawer.add_line("Compose Files:", &total_compose_files.to_string().yellow(), true);
835    box_drawer.add_line("Total Services:", &total_services.to_string().yellow(), true);
836    
837    let patterns = orchestration_patterns.iter()
838        .map(|p| format!("{:?}", p))
839        .collect::<Vec<_>>()
840        .join(", ");
841    box_drawer.add_line("Orchestration Patterns:", &patterns.green(), true);
842    
843    // Service connectivity summary
844    let mut has_services = false;
845    for project in &analysis.projects {
846        if let Some(docker) = &project.analysis.docker_analysis {
847            for service in &docker.services {
848                if !service.ports.is_empty() || !service.depends_on.is_empty() {
849                    has_services = true;
850                    break;
851                }
852            }
853        }
854    }
855    
856    if has_services {
857        box_drawer.add_separator();
858        box_drawer.add_line("Service Connectivity:", "", true);
859        
860        for project in &analysis.projects {
861            if let Some(docker) = &project.analysis.docker_analysis {
862                for service in &docker.services {
863                    if !service.ports.is_empty() || !service.depends_on.is_empty() {
864                        let port_info = service.ports.iter()
865                            .filter_map(|p| p.host_port.map(|hp| format!("{}:{}", hp, p.container_port)))
866                            .collect::<Vec<_>>()
867                            .join(", ");
868                        
869                        let deps_info = if service.depends_on.is_empty() {
870                            String::new()
871                        } else {
872                            format!(" → {}", service.depends_on.join(", "))
873                        };
874                        
875                        let info = format!("  {}: {}{}", service.name, port_info, deps_info);
876                        box_drawer.add_value_only(&info.cyan());
877                    }
878                }
879            }
880        }
881    }
882    
883    println!("\n{}", box_drawer.draw());
884}
885
886/// Display docker overview matrix - returns string
887fn display_docker_overview_matrix_to_string(analysis: &MonorepoAnalysis) -> String {
888    let mut box_drawer = BoxDrawer::new("Docker Infrastructure");
889    
890    let mut total_dockerfiles = 0;
891    let mut total_compose_files = 0;
892    let mut total_services = 0;
893    
894    for project in &analysis.projects {
895        if let Some(docker) = &project.analysis.docker_analysis {
896            total_dockerfiles += docker.dockerfiles.len();
897            total_compose_files += docker.compose_files.len();
898            total_services += docker.services.len();
899        }
900    }
901    
902    box_drawer.add_line("Dockerfiles:", &total_dockerfiles.to_string().yellow(), true);
903    box_drawer.add_line("Compose Files:", &total_compose_files.to_string().yellow(), true);
904    box_drawer.add_line("Total Services:", &total_services.to_string().yellow(), true);
905    
906    format!("\n{}", box_drawer.draw())
907}
908
909/// Display analysis metrics
910fn display_metrics_box(analysis: &MonorepoAnalysis) {
911    let mut box_drawer = BoxDrawer::new("Analysis Metrics");
912    
913    // Performance metrics
914    let duration_ms = analysis.metadata.analysis_duration_ms;
915    let duration_str = if duration_ms < 1000 {
916        format!("{}ms", duration_ms)
917    } else {
918        format!("{:.1}s", duration_ms as f64 / 1000.0)
919    };
920    
921    // Create metrics line without emojis first to avoid width calculation issues
922    let metrics_line = format!(
923        "Duration: {} | Files: {} | Score: {}% | Version: {}",
924        duration_str,
925        analysis.metadata.files_analyzed,
926        format!("{:.0}", analysis.metadata.confidence_score * 100.0),
927        analysis.metadata.analyzer_version
928    );
929    
930    // Apply single color to the entire line for consistency
931    let colored_metrics = metrics_line.cyan();
932    box_drawer.add_value_only(&colored_metrics.to_string());
933    
934    println!("\n{}", box_drawer.draw());
935}
936
937/// Display analysis metrics - returns string
938fn display_metrics_box_to_string(analysis: &MonorepoAnalysis) -> String {
939    let mut box_drawer = BoxDrawer::new("Analysis Metrics");
940    
941    // Performance metrics
942    let duration_ms = analysis.metadata.analysis_duration_ms;
943    let duration_str = if duration_ms < 1000 {
944        format!("{}ms", duration_ms)
945    } else {
946        format!("{:.1}s", duration_ms as f64 / 1000.0)
947    };
948    
949    // Create metrics line
950    let metrics_line = format!(
951        "Duration: {} | Files: {} | Score: {}% | Version: {}",
952        duration_str,
953        analysis.metadata.files_analyzed,
954        format!("{:.0}", analysis.metadata.confidence_score * 100.0),
955        analysis.metadata.analyzer_version
956    );
957    
958    box_drawer.add_value_only(&metrics_line.cyan());
959    
960    format!("\n{}", box_drawer.draw())
961}
962
963/// Add confidence score as a progress bar to the box drawer
964fn add_confidence_bar_to_drawer(score: f32, box_drawer: &mut BoxDrawer) {
965    let percentage = (score * 100.0) as u8;
966    let bar_width = 20;
967    let filled = ((score * bar_width as f32) as usize).min(bar_width);
968    
969    let bar = format!("{}{}",
970        "█".repeat(filled).green(),
971        "░".repeat(bar_width - filled).dimmed()
972    );
973    
974    let color = if percentage >= 80 {
975        "green"
976    } else if percentage >= 60 {
977        "yellow"
978    } else {
979        "red"
980    };
981    
982    let confidence_info = format!("{} {}", bar, format!("{:.0}%", percentage).color(color));
983    box_drawer.add_line("Confidence:", &confidence_info, true);
984}
985
986/// Get main technologies for display
987fn get_main_technologies(technologies: &[DetectedTechnology]) -> String {
988    let primary = technologies.iter().find(|t| t.is_primary);
989    let frameworks: Vec<_> = technologies.iter()
990        .filter(|t| matches!(t.category, TechnologyCategory::FrontendFramework | TechnologyCategory::MetaFramework))
991        .take(2)
992        .collect();
993    
994    let mut result = Vec::new();
995    
996    if let Some(p) = primary {
997        result.push(p.name.clone());
998    }
999    
1000    for f in frameworks {
1001        if Some(&f.name) != primary.map(|p| &p.name) {
1002            result.push(f.name.clone());
1003        }
1004    }
1005    
1006    if result.is_empty() {
1007        "-".to_string()
1008    } else {
1009        result.join(", ")
1010    }
1011}
1012
1013/// Display in detailed vertical format (legacy)
1014pub fn display_detailed_view(analysis: &MonorepoAnalysis) {
1015    // Use the legacy detailed display format
1016    println!("{}", "=".repeat(80));
1017    println!("\n📊 PROJECT ANALYSIS RESULTS");
1018    println!("{}", "=".repeat(80));
1019    
1020    // Overall project information
1021    if analysis.is_monorepo {
1022        println!("\n🏗️  Architecture: Monorepo with {} projects", analysis.projects.len());
1023        println!("   Pattern: {:?}", analysis.technology_summary.architecture_pattern);
1024        
1025        display_architecture_description(&analysis.technology_summary.architecture_pattern);
1026    } else {
1027        println!("\n🏗️  Architecture: Single Project");
1028    }
1029    
1030    // Technology Summary
1031    println!("\n🌐 Technology Summary:");
1032    if !analysis.technology_summary.languages.is_empty() {
1033        println!("   Languages: {}", analysis.technology_summary.languages.join(", "));
1034    }
1035    if !analysis.technology_summary.frameworks.is_empty() {
1036        println!("   Frameworks: {}", analysis.technology_summary.frameworks.join(", "));
1037    }
1038    if !analysis.technology_summary.databases.is_empty() {
1039        println!("   Databases: {}", analysis.technology_summary.databases.join(", "));
1040    }
1041    
1042    // Individual project details
1043    println!("\n📁 Project Details:");
1044    println!("{}", "=".repeat(80));
1045    
1046    for (i, project) in analysis.projects.iter().enumerate() {
1047        println!("\n{} {}. {} ({})", 
1048            get_category_emoji(&project.project_category),
1049            i + 1, 
1050            project.name,
1051            format_project_category(&project.project_category)
1052        );
1053        
1054        if analysis.is_monorepo {
1055            println!("   📂 Path: {}", project.path.display());
1056        }
1057        
1058        // Languages for this project
1059        if !project.analysis.languages.is_empty() {
1060            println!("   🌐 Languages:");
1061            for lang in &project.analysis.languages {
1062                print!("      • {} (confidence: {:.1}%)", lang.name, lang.confidence * 100.0);
1063                if let Some(version) = &lang.version {
1064                    print!(" - Version: {}", version);
1065                }
1066                println!();
1067            }
1068        }
1069        
1070        // Technologies for this project
1071        if !project.analysis.technologies.is_empty() {
1072            println!("   🚀 Technologies:");
1073            display_technologies_detailed_legacy(&project.analysis.technologies);
1074        }
1075        
1076        // Entry Points
1077        if !project.analysis.entry_points.is_empty() {
1078            println!("   📍 Entry Points ({}):", project.analysis.entry_points.len());
1079            for (j, entry) in project.analysis.entry_points.iter().enumerate() {
1080                println!("      {}. File: {}", j + 1, entry.file.display());
1081                if let Some(func) = &entry.function {
1082                    println!("         Function: {}", func);
1083                }
1084                if let Some(cmd) = &entry.command {
1085                    println!("         Command: {}", cmd);
1086                }
1087            }
1088        }
1089        
1090        // Ports
1091        if !project.analysis.ports.is_empty() {
1092            println!("   🔌 Exposed Ports ({}):", project.analysis.ports.len());
1093            for port in &project.analysis.ports {
1094                println!("      • Port {}: {:?}", port.number, port.protocol);
1095                if let Some(desc) = &port.description {
1096                    println!("        {}", desc);
1097                }
1098            }
1099        }
1100        
1101        // Environment Variables
1102        if !project.analysis.environment_variables.is_empty() {
1103            println!("   🔐 Environment Variables ({}):", project.analysis.environment_variables.len());
1104            let required_vars: Vec<_> = project.analysis.environment_variables.iter()
1105                .filter(|ev| ev.required)
1106                .collect();
1107            let optional_vars: Vec<_> = project.analysis.environment_variables.iter()
1108                .filter(|ev| !ev.required)
1109                .collect();
1110            
1111            if !required_vars.is_empty() {
1112                println!("      Required:");
1113                for var in required_vars {
1114                    println!("        • {} {}", 
1115                        var.name,
1116                        if let Some(desc) = &var.description { 
1117                            format!("({})", desc) 
1118                        } else { 
1119                            String::new() 
1120                        }
1121                    );
1122                }
1123            }
1124            
1125            if !optional_vars.is_empty() {
1126                println!("      Optional:");
1127                for var in optional_vars {
1128                    println!("        • {} = {:?}", 
1129                        var.name, 
1130                        var.default_value.as_deref().unwrap_or("no default")
1131                    );
1132                }
1133            }
1134        }
1135        
1136        // Build Scripts
1137        if !project.analysis.build_scripts.is_empty() {
1138            println!("   🔨 Build Scripts ({}):", project.analysis.build_scripts.len());
1139            let default_scripts: Vec<_> = project.analysis.build_scripts.iter()
1140                .filter(|bs| bs.is_default)
1141                .collect();
1142            let other_scripts: Vec<_> = project.analysis.build_scripts.iter()
1143                .filter(|bs| !bs.is_default)
1144                .collect();
1145            
1146            if !default_scripts.is_empty() {
1147                println!("      Default scripts:");
1148                for script in default_scripts {
1149                    println!("        • {}: {}", script.name, script.command);
1150                    if let Some(desc) = &script.description {
1151                        println!("          {}", desc);
1152                    }
1153                }
1154            }
1155            
1156            if !other_scripts.is_empty() {
1157                println!("      Other scripts:");
1158                for script in other_scripts {
1159                    println!("        • {}: {}", script.name, script.command);
1160                    if let Some(desc) = &script.description {
1161                        println!("          {}", desc);
1162                    }
1163                }
1164            }
1165        }
1166        
1167        // Dependencies (sample)
1168        if !project.analysis.dependencies.is_empty() {
1169            println!("   📦 Dependencies ({}):", project.analysis.dependencies.len());
1170            if project.analysis.dependencies.len() <= 5 {
1171                for (name, version) in &project.analysis.dependencies {
1172                    println!("      • {} v{}", name, version);
1173                }
1174            } else {
1175                // Show first 5
1176                for (name, version) in project.analysis.dependencies.iter().take(5) {
1177                    println!("      • {} v{}", name, version);
1178                }
1179                println!("      ... and {} more", project.analysis.dependencies.len() - 5);
1180            }
1181        }
1182        
1183        // Docker Infrastructure Analysis
1184        if let Some(docker_analysis) = &project.analysis.docker_analysis {
1185            display_docker_analysis_detailed_legacy(docker_analysis);
1186        }
1187        
1188        // Project type
1189        println!("   🎯 Project Type: {:?}", project.analysis.project_type);
1190        
1191        if i < analysis.projects.len() - 1 {
1192            println!("{}", "-".repeat(40));
1193        }
1194    }
1195    
1196    // Summary
1197    println!("\n📋 ANALYSIS SUMMARY");
1198    println!("{}", "=".repeat(80));
1199    println!("✅ Project Analysis Complete!");
1200    
1201    if analysis.is_monorepo {
1202        println!("\n🏗️  Monorepo Architecture:");
1203        println!("   • Total projects: {}", analysis.projects.len());
1204        println!("   • Architecture pattern: {:?}", analysis.technology_summary.architecture_pattern);
1205        
1206        let frontend_count = analysis.projects.iter().filter(|p| p.project_category == ProjectCategory::Frontend).count();
1207        let backend_count = analysis.projects.iter().filter(|p| matches!(p.project_category, ProjectCategory::Backend | ProjectCategory::Api)).count();
1208        let service_count = analysis.projects.iter().filter(|p| p.project_category == ProjectCategory::Service).count();
1209        let lib_count = analysis.projects.iter().filter(|p| p.project_category == ProjectCategory::Library).count();
1210        
1211        if frontend_count > 0 { println!("   • Frontend projects: {}", frontend_count); }
1212        if backend_count > 0 { println!("   • Backend/API projects: {}", backend_count); }
1213        if service_count > 0 { println!("   • Service projects: {}", service_count); }
1214        if lib_count > 0 { println!("   • Library projects: {}", lib_count); }
1215    }
1216    
1217    println!("\n📈 Analysis Metadata:");
1218    println!("   • Duration: {}ms", analysis.metadata.analysis_duration_ms);
1219    println!("   • Files analyzed: {}", analysis.metadata.files_analyzed);
1220    println!("   • Confidence score: {:.1}%", analysis.metadata.confidence_score * 100.0);
1221    println!("   • Analyzer version: {}", analysis.metadata.analyzer_version);
1222}
1223
1224/// Helper function for legacy detailed technology display
1225fn display_technologies_detailed_legacy(technologies: &[DetectedTechnology]) {
1226    // Group technologies by category
1227    let mut by_category: std::collections::HashMap<&TechnologyCategory, Vec<&DetectedTechnology>> = std::collections::HashMap::new();
1228    
1229    for tech in technologies {
1230        by_category.entry(&tech.category).or_insert_with(Vec::new).push(tech);
1231    }
1232    
1233    // Find and display primary technology
1234    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
1235        println!("\n🛠️  Technology Stack:");
1236        println!("   🎯 PRIMARY: {} (confidence: {:.1}%)", primary.name, primary.confidence * 100.0);
1237        println!("      Architecture driver for this project");
1238    }
1239    
1240    // Display categories in order
1241    let categories = [
1242        (TechnologyCategory::MetaFramework, "🏗️  Meta-Frameworks"),
1243        (TechnologyCategory::BackendFramework, "🖥️  Backend Frameworks"),
1244        (TechnologyCategory::FrontendFramework, "🎨 Frontend Frameworks"),
1245        (TechnologyCategory::Library(LibraryType::UI), "🎨 UI Libraries"),
1246        (TechnologyCategory::Library(LibraryType::Utility), "📚 Core Libraries"),
1247        (TechnologyCategory::BuildTool, "🔨 Build Tools"),
1248        (TechnologyCategory::PackageManager, "📦 Package Managers"),
1249        (TechnologyCategory::Database, "🗃️  Database & ORM"),
1250        (TechnologyCategory::Runtime, "⚡ Runtimes"),
1251        (TechnologyCategory::Testing, "🧪 Testing"),
1252    ];
1253    
1254    for (category, label) in &categories {
1255        if let Some(techs) = by_category.get(category) {
1256            if !techs.is_empty() {
1257                println!("\n   {}:", label);
1258                for tech in techs {
1259                    println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
1260                    if let Some(version) = &tech.version {
1261                        println!("        Version: {}", version);
1262                    }
1263                }
1264            }
1265        }
1266    }
1267    
1268    // Handle other Library types separately
1269    for (cat, techs) in &by_category {
1270        match cat {
1271            TechnologyCategory::Library(lib_type) => {
1272                let label = match lib_type {
1273                    LibraryType::StateManagement => "🔄 State Management",
1274                    LibraryType::DataFetching => "🔃 Data Fetching",
1275                    LibraryType::Routing => "🗺️  Routing",
1276                    LibraryType::Styling => "🎨 Styling",
1277                    LibraryType::HttpClient => "🌐 HTTP Clients",
1278                    LibraryType::Authentication => "🔐 Authentication",
1279                    LibraryType::Other(_) => "📦 Other Libraries",
1280                    _ => continue, // Skip already handled UI and Utility
1281                };
1282                
1283                // Only print if not already handled above
1284                if !matches!(lib_type, LibraryType::UI | LibraryType::Utility) && !techs.is_empty() {
1285                    println!("\n   {}:", label);
1286                    for tech in techs {
1287                        println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
1288                        if let Some(version) = &tech.version {
1289                            println!("        Version: {}", version);
1290                        }
1291                    }
1292                }
1293            }
1294            _ => {} // Other categories already handled in the array
1295        }
1296    }
1297}
1298
1299/// Helper function for legacy Docker analysis display
1300fn display_docker_analysis_detailed_legacy(docker_analysis: &DockerAnalysis) {
1301    println!("\n   🐳 Docker Infrastructure Analysis:");
1302    
1303    // Dockerfiles
1304    if !docker_analysis.dockerfiles.is_empty() {
1305        println!("      📄 Dockerfiles ({}):", docker_analysis.dockerfiles.len());
1306        for dockerfile in &docker_analysis.dockerfiles {
1307            println!("         • {}", dockerfile.path.display());
1308            if let Some(env) = &dockerfile.environment {
1309                println!("           Environment: {}", env);
1310            }
1311            if let Some(base_image) = &dockerfile.base_image {
1312                println!("           Base image: {}", base_image);
1313            }
1314            if !dockerfile.exposed_ports.is_empty() {
1315                println!("           Exposed ports: {}", 
1316                    dockerfile.exposed_ports.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", "));
1317            }
1318            if dockerfile.is_multistage {
1319                println!("           Multi-stage build: {} stages", dockerfile.build_stages.len());
1320            }
1321            println!("           Instructions: {}", dockerfile.instruction_count);
1322        }
1323    }
1324    
1325    // Compose files
1326    if !docker_analysis.compose_files.is_empty() {
1327        println!("      📋 Compose Files ({}):", docker_analysis.compose_files.len());
1328        for compose_file in &docker_analysis.compose_files {
1329            println!("         • {}", compose_file.path.display());
1330            if let Some(env) = &compose_file.environment {
1331                println!("           Environment: {}", env);
1332            }
1333            if let Some(version) = &compose_file.version {
1334                println!("           Version: {}", version);
1335            }
1336            if !compose_file.service_names.is_empty() {
1337                println!("           Services: {}", compose_file.service_names.join(", "));
1338            }
1339            if !compose_file.networks.is_empty() {
1340                println!("           Networks: {}", compose_file.networks.join(", "));
1341            }
1342            if !compose_file.volumes.is_empty() {
1343                println!("           Volumes: {}", compose_file.volumes.join(", "));
1344            }
1345        }
1346    }
1347    
1348    // Rest of the detailed Docker display...
1349    println!("      🏗️  Orchestration Pattern: {:?}", docker_analysis.orchestration_pattern);
1350    match docker_analysis.orchestration_pattern {
1351        OrchestrationPattern::SingleContainer => {
1352            println!("         Simple containerized application");
1353        }
1354        OrchestrationPattern::DockerCompose => {
1355            println!("         Multi-service Docker Compose setup");
1356        }
1357        OrchestrationPattern::Microservices => {
1358            println!("         Microservices architecture with service discovery");
1359        }
1360        OrchestrationPattern::EventDriven => {
1361            println!("         Event-driven architecture with message queues");
1362        }
1363        OrchestrationPattern::ServiceMesh => {
1364            println!("         Service mesh for advanced service communication");
1365        }
1366        OrchestrationPattern::Mixed => {
1367            println!("         Mixed/complex orchestration pattern");
1368        }
1369    }
1370}
1371
1372/// Display architecture description
1373fn display_architecture_description(pattern: &ArchitecturePattern) {
1374    match pattern {
1375        ArchitecturePattern::Monolithic => {
1376            println!("   📦 This is a single, self-contained application");
1377        }
1378        ArchitecturePattern::Fullstack => {
1379            println!("   🌐 This is a full-stack application with separate frontend and backend");
1380        }
1381        ArchitecturePattern::Microservices => {
1382            println!("   🔗 This is a microservices architecture with multiple independent services");
1383        }
1384        ArchitecturePattern::ApiFirst => {
1385            println!("   🔌 This is an API-first architecture focused on service interfaces");
1386        }
1387        ArchitecturePattern::EventDriven => {
1388            println!("   📡 This is an event-driven architecture with decoupled components");
1389        }
1390        ArchitecturePattern::Mixed => {
1391            println!("   🔀 This is a mixed architecture combining multiple patterns");
1392        }
1393    }
1394}
1395
1396/// Display summary view only
1397pub fn display_summary_view(analysis: &MonorepoAnalysis) {
1398    println!("\n{} {}", "▶".bright_blue(), "PROJECT ANALYSIS SUMMARY".bright_white().bold());
1399    println!("{}", "─".repeat(50).dimmed());
1400    
1401    println!("{} Architecture: {}", "│".dimmed(), 
1402        if analysis.is_monorepo {
1403            format!("Monorepo ({} projects)", analysis.projects.len()).yellow()
1404        } else {
1405            "Single Project".to_string().yellow()
1406        }
1407    );
1408    
1409    println!("{} Pattern: {}", "│".dimmed(), format!("{:?}", analysis.technology_summary.architecture_pattern).green());
1410    println!("{} Stack: {}", "│".dimmed(), analysis.technology_summary.languages.join(", ").blue());
1411    
1412    if !analysis.technology_summary.frameworks.is_empty() {
1413        println!("{} Frameworks: {}", "│".dimmed(), analysis.technology_summary.frameworks.join(", ").magenta());
1414    }
1415    
1416    println!("{} Analysis Time: {}ms", "│".dimmed(), analysis.metadata.analysis_duration_ms);
1417    println!("{} Confidence: {:.0}%", "│".dimmed(), analysis.metadata.confidence_score * 100.0);
1418    
1419    println!("{}", "─".repeat(50).dimmed());
1420}
1421
1422/// Display JSON output
1423pub fn display_json_view(analysis: &MonorepoAnalysis) {
1424    match serde_json::to_string_pretty(analysis) {
1425        Ok(json) => println!("{}", json),
1426        Err(e) => eprintln!("Error serializing to JSON: {}", e),
1427    }
1428}
1429
1430/// Display JSON output - returns string
1431pub fn display_json_view_to_string(analysis: &MonorepoAnalysis) -> String {
1432    match serde_json::to_string_pretty(analysis) {
1433        Ok(json) => json,
1434        Err(e) => format!("Error serializing to JSON: {}", e),
1435    }
1436}
1437
1438/// Display summary view - returns string
1439pub fn display_summary_view_to_string(analysis: &MonorepoAnalysis) -> String {
1440    let mut output = String::new();
1441    
1442    output.push_str(&format!("\n{} {}\n", "▶".bright_blue(), "PROJECT ANALYSIS SUMMARY".bright_white().bold()));
1443    output.push_str(&format!("{}\n", "─".repeat(50).dimmed()));
1444    
1445    output.push_str(&format!("{} Architecture: {}\n", "│".dimmed(), 
1446        if analysis.is_monorepo {
1447            format!("Monorepo ({} projects)", analysis.projects.len()).yellow()
1448        } else {
1449            "Single Project".to_string().yellow()
1450        }
1451    ));
1452    
1453    output.push_str(&format!("{} Pattern: {}\n", "│".dimmed(), format!("{:?}", analysis.technology_summary.architecture_pattern).green()));
1454    output.push_str(&format!("{} Stack: {}\n", "│".dimmed(), analysis.technology_summary.languages.join(", ").blue()));
1455    
1456    if !analysis.technology_summary.frameworks.is_empty() {
1457        output.push_str(&format!("{} Frameworks: {}\n", "│".dimmed(), analysis.technology_summary.frameworks.join(", ").magenta()));
1458    }
1459    
1460    output.push_str(&format!("{} Analysis Time: {}ms\n", "│".dimmed(), analysis.metadata.analysis_duration_ms));
1461    output.push_str(&format!("{} Confidence: {:.0}%\n", "│".dimmed(), analysis.metadata.confidence_score * 100.0));
1462    
1463    output.push_str(&format!("{}\n", "─".repeat(50).dimmed()));
1464    
1465    output
1466}
1467
1468/// Display detailed view - returns string  
1469pub fn display_detailed_view_to_string(analysis: &MonorepoAnalysis) -> String {
1470    let mut output = String::new();
1471    
1472    output.push_str(&format!("{}\n", "=".repeat(80)));
1473    output.push_str("\n📊 PROJECT ANALYSIS RESULTS\n");
1474    output.push_str(&format!("{}\n", "=".repeat(80)));
1475    
1476    // Overall project information
1477    if analysis.is_monorepo {
1478        output.push_str(&format!("\n🏗️  Architecture: Monorepo with {} projects\n", analysis.projects.len()));
1479        output.push_str(&format!("   Pattern: {:?}\n", analysis.technology_summary.architecture_pattern));
1480        
1481        output.push_str(&display_architecture_description_to_string(&analysis.technology_summary.architecture_pattern));
1482    } else {
1483        output.push_str("\n🏗️  Architecture: Single Project\n");
1484    }
1485    
1486    // Technology Summary
1487    output.push_str("\n🌐 Technology Summary:\n");
1488    if !analysis.technology_summary.languages.is_empty() {
1489        output.push_str(&format!("   Languages: {}\n", analysis.technology_summary.languages.join(", ")));
1490    }
1491    if !analysis.technology_summary.frameworks.is_empty() {
1492        output.push_str(&format!("   Frameworks: {}\n", analysis.technology_summary.frameworks.join(", ")));
1493    }
1494    if !analysis.technology_summary.databases.is_empty() {
1495        output.push_str(&format!("   Databases: {}\n", analysis.technology_summary.databases.join(", ")));
1496    }
1497    
1498    // Individual project details - simplified version
1499    output.push_str("\n📁 Project Details:\n");
1500    output.push_str(&format!("{}\n", "=".repeat(80)));
1501    
1502    for (i, project) in analysis.projects.iter().enumerate() {
1503        output.push_str(&format!("\n{} {}. {} ({})\n", 
1504            get_category_emoji(&project.project_category),
1505            i + 1, 
1506            project.name,
1507            format_project_category(&project.project_category)
1508        ));
1509        
1510        if analysis.is_monorepo {
1511            output.push_str(&format!("   📂 Path: {}\n", project.path.display()));
1512        }
1513        
1514        // Languages for this project
1515        if !project.analysis.languages.is_empty() {
1516            output.push_str("   🌐 Languages:\n");
1517            for lang in &project.analysis.languages {
1518                output.push_str(&format!("      • {} (confidence: {:.1}%)", lang.name, lang.confidence * 100.0));
1519                if let Some(version) = &lang.version {
1520                    output.push_str(&format!(" - Version: {}", version));
1521                }
1522                output.push('\n');
1523            }
1524        }
1525        
1526        if i < analysis.projects.len() - 1 {
1527            output.push_str(&format!("{}\n", "-".repeat(40)));
1528        }
1529    }
1530    
1531    // Summary
1532    output.push_str("\n📋 ANALYSIS SUMMARY\n");
1533    output.push_str(&format!("{}\n", "=".repeat(80)));
1534    output.push_str("✅ Project Analysis Complete!\n");
1535    
1536    output.push_str("\n📈 Analysis Metadata:\n");  
1537    output.push_str(&format!("   • Duration: {}ms\n", analysis.metadata.analysis_duration_ms));
1538    output.push_str(&format!("   • Files analyzed: {}\n", analysis.metadata.files_analyzed));
1539    output.push_str(&format!("   • Confidence score: {:.1}%\n", analysis.metadata.confidence_score * 100.0));
1540    output.push_str(&format!("   • Analyzer version: {}\n", analysis.metadata.analyzer_version));
1541    
1542    output
1543}
1544
1545/// Helper function for displaying architecture description - returns string
1546fn display_architecture_description_to_string(pattern: &ArchitecturePattern) -> String {
1547    match pattern {
1548        ArchitecturePattern::Monolithic => {
1549            "   📦 This is a single, self-contained application\n".to_string()
1550        }
1551        ArchitecturePattern::Fullstack => {
1552            "   🌐 This is a full-stack application with separate frontend and backend\n".to_string()
1553        }
1554        ArchitecturePattern::Microservices => {
1555            "   🔗 This is a microservices architecture with multiple independent services\n".to_string()
1556        }
1557        ArchitecturePattern::ApiFirst => {
1558            "   🔌 This is an API-first architecture focused on service interfaces\n".to_string()
1559        }
1560        ArchitecturePattern::EventDriven => {
1561            "   📡 This is an event-driven architecture with decoupled components\n".to_string()
1562        }
1563        ArchitecturePattern::Mixed => {
1564            "   🔀 This is a mixed architecture combining multiple patterns\n".to_string()
1565        }
1566    }
1567}
1568
1569/// Get emoji for project category
1570fn get_category_emoji(category: &ProjectCategory) -> &'static str {
1571    match category {
1572        ProjectCategory::Frontend => "🌐",
1573        ProjectCategory::Backend => "⚙️",
1574        ProjectCategory::Api => "🔌",
1575        ProjectCategory::Service => "🚀",
1576        ProjectCategory::Library => "📚",
1577        ProjectCategory::Tool => "🔧",
1578        ProjectCategory::Documentation => "📖",
1579        ProjectCategory::Infrastructure => "🏗️",
1580        ProjectCategory::Unknown => "❓",
1581    }
1582}
1583
1584/// Format project category name
1585fn format_project_category(category: &ProjectCategory) -> &'static str {
1586    match category {
1587        ProjectCategory::Frontend => "Frontend",
1588        ProjectCategory::Backend => "Backend",
1589        ProjectCategory::Api => "API",
1590        ProjectCategory::Service => "Service",
1591        ProjectCategory::Library => "Library",
1592        ProjectCategory::Tool => "Tool",
1593        ProjectCategory::Documentation => "Documentation",
1594        ProjectCategory::Infrastructure => "Infrastructure",
1595        ProjectCategory::Unknown => "Unknown",
1596    }
1597}
1598
1599/// Add technologies organized by category to the box drawer
1600fn add_technologies_to_drawer(technologies: &[DetectedTechnology], box_drawer: &mut BoxDrawer) {
1601    let mut by_category: std::collections::HashMap<&TechnologyCategory, Vec<&DetectedTechnology>> = std::collections::HashMap::new();
1602    
1603    for tech in technologies {
1604        by_category.entry(&tech.category).or_insert_with(Vec::new).push(tech);
1605    }
1606    
1607    // Display primary technology first
1608    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
1609        let primary_info = primary.name.bright_yellow().bold().to_string();
1610        box_drawer.add_line("Primary Stack:", &primary_info, true);
1611    }
1612    
1613    // Display other categories
1614    let categories = [
1615        (TechnologyCategory::FrontendFramework, "Frameworks"),
1616        (TechnologyCategory::BuildTool, "Build Tools"),
1617        (TechnologyCategory::Database, "Databases"),
1618        (TechnologyCategory::Testing, "Testing"),
1619    ];
1620    
1621    for (category, label) in &categories {
1622        if let Some(techs) = by_category.get(category) {
1623            let tech_names = techs.iter()
1624                .map(|t| t.name.clone())
1625                .collect::<Vec<_>>()
1626                .join(", ");
1627            
1628            if !tech_names.is_empty() {
1629                let label_with_colon = format!("{}:", label);
1630                box_drawer.add_line(&label_with_colon, &tech_names.magenta(), true);
1631            }
1632        }
1633    }
1634    
1635    // Handle Library category separately since it's parameterized - use vertical layout for many items
1636    let mut all_libraries: Vec<&DetectedTechnology> = Vec::new();
1637    for (cat, techs) in &by_category {
1638        if matches!(cat, TechnologyCategory::Library(_)) {
1639            all_libraries.extend(techs.iter().copied());
1640        }
1641    }
1642    
1643    if !all_libraries.is_empty() {
1644        // Sort libraries by confidence for better display
1645        all_libraries.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap_or(std::cmp::Ordering::Equal));
1646        
1647        if all_libraries.len() <= 3 {
1648            // For few libraries, keep horizontal layout
1649            let tech_names = all_libraries.iter()
1650                .map(|t| t.name.clone())
1651                .collect::<Vec<_>>()
1652                .join(", ");
1653            box_drawer.add_line("Libraries:", &tech_names.magenta(), true);
1654        } else {
1655            // For many libraries, use vertical layout with multiple rows
1656            box_drawer.add_line("Libraries:", "", true);
1657            
1658            // Group libraries into rows of 3-4 items each
1659            let items_per_row = 3;
1660            for chunk in all_libraries.chunks(items_per_row) {
1661                let row_items = chunk.iter()
1662                    .map(|t| t.name.clone())
1663                    .collect::<Vec<_>>()
1664                    .join(", ");
1665                
1666                // Add indented row
1667                let indented_row = format!("  {}", row_items);
1668                box_drawer.add_value_only(&indented_row.magenta());
1669            }
1670        }
1671    }
1672}
1673
1674#[cfg(test)]
1675mod tests {
1676    use super::*;
1677    
1678    #[test]
1679    fn test_display_modes() {
1680        // Test that display modes are properly defined
1681        assert_eq!(DisplayMode::Matrix, DisplayMode::Matrix);
1682        assert_ne!(DisplayMode::Matrix, DisplayMode::Detailed);
1683    }
1684}