garbage_code_hunter/
reporter.rs

1#[allow(dead_code)]
2use colored::*;
3use std::collections::HashMap;
4
5use crate::analyzer::{CodeIssue, Severity};
6use crate::i18n::I18n;
7use crate::scoring::{CodeQualityScore, CodeScorer};
8
9pub struct Reporter {
10    harsh_mode: bool,
11    savage_mode: bool,
12    verbose: bool,
13    top_files: usize,
14    max_issues_per_file: usize,
15    summary_only: bool,
16    markdown: bool,
17    i18n: I18n,
18}
19
20#[allow(dead_code)]
21impl Reporter {
22    #[allow(clippy::too_many_arguments)]
23    pub fn new(
24        harsh_mode: bool,
25        savage_mode: bool,
26        verbose: bool,
27        top_files: usize,
28        max_issues_per_file: usize,
29        summary_only: bool,
30        markdown: bool,
31        lang: &str,
32    ) -> Self {
33        Self {
34            harsh_mode,
35            savage_mode,
36            verbose,
37            top_files,
38            max_issues_per_file,
39            summary_only,
40            markdown,
41            i18n: I18n::new(lang),
42        }
43    }
44
45    #[allow(dead_code)]
46    pub fn report(&self, issues: Vec<CodeIssue>) {
47        self.report_with_metrics(issues, 1, 100);
48    }
49
50    pub fn report_with_metrics(
51        &self,
52        mut issues: Vec<CodeIssue>,
53        file_count: usize,
54        total_lines: usize,
55    ) {
56        // 计算代码质量评分
57        let scorer = CodeScorer::new();
58        let quality_score = scorer.calculate_score(&issues, file_count, total_lines);
59
60        if issues.is_empty() {
61            self.print_clean_code_message_with_score(&quality_score);
62            return;
63        }
64
65        // 按严重程度排序
66        issues.sort_by(|a, b| {
67            let severity_order = |s: &Severity| match s {
68                Severity::Nuclear => 3,
69                Severity::Spicy => 2,
70                Severity::Mild => 1,
71            };
72            severity_order(&b.severity).cmp(&severity_order(&a.severity))
73        });
74
75        // 如果是 harsh 模式,只显示最严重的问题
76        if self.harsh_mode {
77            issues.retain(|issue| matches!(issue.severity, Severity::Nuclear | Severity::Spicy));
78        }
79
80        if self.markdown {
81            self.print_markdown_report(&issues);
82        } else {
83            if !self.summary_only {
84                self.print_header(&issues);
85                self.print_quality_score(&quality_score);
86                if self.verbose {
87                    self.print_detailed_analysis(&issues);
88                }
89                self.print_top_files(&issues);
90                self.print_issues(&issues);
91            }
92            self.print_summary_with_score(&issues, &quality_score);
93            if !self.summary_only {
94                self.print_footer(&issues);
95            }
96        }
97    }
98
99    #[allow(dead_code)]
100    fn print_clean_code_message(&self) {
101        if self.markdown {
102            println!("# {}", self.i18n.get("title"));
103            println!();
104            println!("{}", self.i18n.get("clean_code"));
105            println!();
106            println!("{}", self.i18n.get("clean_code_warning"));
107        } else {
108            println!("{}", self.i18n.get("clean_code").bright_green().bold());
109            println!("{}", self.i18n.get("clean_code_warning").yellow());
110        }
111    }
112
113    fn print_clean_code_message_with_score(&self, quality_score: &CodeQualityScore) {
114        if self.markdown {
115            println!("# {}", self.i18n.get("title"));
116            println!();
117            println!("## 🏆 代码质量评分");
118            println!();
119            println!(
120                "**评分**: {:.1}/100 {}",
121                quality_score.total_score,
122                quality_score.quality_level.emoji()
123            );
124            println!(
125                "**等级**: {}",
126                quality_score.quality_level.description(&self.i18n.lang)
127            );
128            println!();
129            println!("{}", self.i18n.get("clean_code"));
130            println!();
131            println!("{}", self.i18n.get("clean_code_warning"));
132        } else {
133            println!("{}", self.i18n.get("clean_code").bright_green().bold());
134            println!();
135            println!(
136                "{} 代码质量评分: {:.1}/100 {}",
137                "🏆".bright_yellow(),
138                quality_score.total_score.to_string().bright_green().bold(),
139                quality_score.quality_level.emoji()
140            );
141            println!(
142                "{} 质量等级: {}",
143                "📊".bright_blue(),
144                quality_score
145                    .quality_level
146                    .description(&self.i18n.lang)
147                    .bright_green()
148                    .bold()
149            );
150            println!("{}", self.i18n.get("clean_code_warning").yellow());
151        }
152    }
153
154    fn print_quality_score(&self, quality_score: &CodeQualityScore) {
155        let title = match self.i18n.lang.as_str() {
156            "zh-CN" => "🏆 代码质量评分",
157            _ => "🏆 Code Quality Score",
158        };
159        println!("{}", title.bright_yellow().bold());
160        println!("{}", "─".repeat(50).bright_black());
161
162        let _score_color = match quality_score.quality_level {
163            crate::scoring::QualityLevel::Excellent => {
164                quality_score.total_score.to_string().bright_green().bold()
165            }
166            crate::scoring::QualityLevel::Good => quality_score.total_score.to_string().green(),
167            crate::scoring::QualityLevel::Average => quality_score.total_score.to_string().yellow(),
168            crate::scoring::QualityLevel::Poor => quality_score.total_score.to_string().red(),
169            crate::scoring::QualityLevel::Terrible => {
170                quality_score.total_score.to_string().bright_red().bold()
171            }
172        };
173
174        let (score_label, level_label) = match self.i18n.lang.as_str() {
175            "zh-CN" => ("📊 总分", "🎯 等级"),
176            _ => ("📊 Score", "🎯 Level"),
177        };
178
179        println!(
180            "   {}: {:.1}/100 {}",
181            score_label,
182            quality_score.total_score,
183            quality_score.quality_level.emoji()
184        );
185        println!(
186            "   {}: {}",
187            level_label,
188            quality_score
189                .quality_level
190                .description(&self.i18n.lang)
191                .bright_white()
192                .bold()
193        );
194
195        if quality_score.total_lines > 0 {
196            let (lines_label, files_label, density_label) = match self.i18n.lang.as_str() {
197                "zh-CN" => ("📏 代码行数", "📁 文件数量", "🔍 问题密度"),
198                _ => ("📏 Lines of Code", "📁 Files", "🔍 Issue Density"),
199            };
200            let density_unit = match self.i18n.lang.as_str() {
201                "zh-CN" => "问题/千行",
202                _ => "issues/1k lines",
203            };
204
205            println!(
206                "   {}: {}",
207                lines_label,
208                quality_score.total_lines.to_string().cyan()
209            );
210            println!(
211                "   {}: {}",
212                files_label,
213                quality_score.file_count.to_string().cyan()
214            );
215            println!(
216                "   {}: {:.2} {}",
217                density_label,
218                quality_score.issue_density.to_string().cyan(),
219                density_unit
220            );
221        }
222
223        // 显示严重程度分布
224        if quality_score.severity_distribution.nuclear > 0
225            || quality_score.severity_distribution.spicy > 0
226            || quality_score.severity_distribution.mild > 0
227        {
228            println!();
229            let distribution_title = match self.i18n.lang.as_str() {
230                "zh-CN" => "🎭 问题分布:",
231                _ => "🎭 Issue Distribution:",
232            };
233            let (nuclear_label, spicy_label, mild_label) = match self.i18n.lang.as_str() {
234                "zh-CN" => ("💥 核弹级", "🌶️  严重", "😐 轻微"),
235                _ => ("💥 Nuclear", "🌶️  Spicy", "😐 Mild"),
236            };
237
238            println!("   {distribution_title}");
239            if quality_score.severity_distribution.nuclear > 0 {
240                println!(
241                    "      {}: {}",
242                    nuclear_label,
243                    quality_score
244                        .severity_distribution
245                        .nuclear
246                        .to_string()
247                        .red()
248                        .bold()
249                );
250            }
251            if quality_score.severity_distribution.spicy > 0 {
252                println!(
253                    "      {}: {}",
254                    spicy_label,
255                    quality_score
256                        .severity_distribution
257                        .spicy
258                        .to_string()
259                        .yellow()
260                );
261            }
262            if quality_score.severity_distribution.mild > 0 {
263                println!(
264                    "      {}: {}",
265                    mild_label,
266                    quality_score.severity_distribution.mild.to_string().blue()
267                );
268            }
269        }
270
271        // 显示分类得分(如果有的话)
272        if !quality_score.category_scores.is_empty() && self.verbose {
273            println!();
274            let category_title = match self.i18n.lang.as_str() {
275                "zh-CN" => "📋 分类得分:",
276                _ => "📋 Category Scores:",
277            };
278            println!("   {category_title}");
279            let mut sorted_categories: Vec<_> = quality_score.category_scores.iter().collect();
280            sorted_categories
281                .sort_by(|a, b| b.1.partial_cmp(a.1).unwrap_or(std::cmp::Ordering::Equal));
282
283            for (category, score) in sorted_categories.iter().take(5) {
284                let category_name = match (self.i18n.lang.as_str(), category.as_str()) {
285                    ("zh-CN", "naming") => "命名规范",
286                    ("zh-CN", "complexity") => "复杂度",
287                    ("zh-CN", "rust-basics") => "Rust基础",
288                    ("zh-CN", "advanced-rust") => "高级特性",
289                    ("zh-CN", "rust-features") => "Rust功能",
290                    ("zh-CN", "structure") => "代码结构",
291                    ("zh-CN", "duplication") => "重复代码",
292                    (_, "naming") => "Naming",
293                    (_, "complexity") => "Complexity",
294                    (_, "rust-basics") => "Rust Basics",
295                    (_, "advanced-rust") => "Advanced Rust",
296                    (_, "rust-features") => "Rust Features",
297                    (_, "structure") => "Code Structure",
298                    (_, "duplication") => "Code Duplication",
299                    _ => category,
300                };
301                println!(
302                    "      {} {:.1}",
303                    category_name.cyan(),
304                    score.to_string().yellow()
305                );
306            }
307        }
308
309        println!();
310    }
311
312    fn print_header(&self, issues: &[CodeIssue]) {
313        let total = issues.len();
314        let nuclear = issues
315            .iter()
316            .filter(|i| matches!(i.severity, Severity::Nuclear))
317            .count();
318        let spicy = issues
319            .iter()
320            .filter(|i| matches!(i.severity, Severity::Spicy))
321            .count();
322        let mild = issues
323            .iter()
324            .filter(|i| matches!(i.severity, Severity::Mild))
325            .count();
326
327        println!("{}", self.i18n.get("title").bright_red().bold());
328        println!("{}", self.i18n.get("preparing").yellow());
329        println!();
330
331        println!("{}", self.i18n.get("report_title").bright_red().bold());
332        println!("{}", "─".repeat(50).bright_black());
333
334        if self.savage_mode {
335            println!("{}", self.i18n.get("found_issues").red().bold());
336        } else {
337            println!("{}", self.i18n.get("found_issues").yellow());
338        }
339
340        println!();
341        println!("{}", self.i18n.get("statistics"));
342        println!(
343            "   {} {}",
344            nuclear.to_string().red().bold(),
345            self.i18n.get("nuclear_issues")
346        );
347        println!(
348            "   {} {}",
349            spicy.to_string().yellow().bold(),
350            self.i18n.get("spicy_issues")
351        );
352        println!(
353            "   {} {}",
354            mild.to_string().blue().bold(),
355            self.i18n.get("mild_issues")
356        );
357        println!(
358            "   {} {}",
359            total.to_string().bright_white().bold(),
360            self.i18n.get("total")
361        );
362        println!();
363    }
364
365    fn print_issues(&self, issues: &[CodeIssue]) {
366        let mut file_groups: HashMap<String, Vec<&CodeIssue>> = HashMap::new();
367
368        for issue in issues {
369            let file_name = issue
370                .file_path
371                .file_name()
372                .unwrap_or_default()
373                .to_string_lossy()
374                .to_string();
375            file_groups.entry(file_name).or_default().push(issue);
376        }
377
378        for (file_name, file_issues) in file_groups {
379            println!("{} {}", "📁".bright_blue(), file_name.bright_blue().bold());
380
381            // Group issues by rule type
382            let mut rule_groups: HashMap<String, Vec<&CodeIssue>> = HashMap::new();
383            for issue in &file_issues {
384                rule_groups
385                    .entry(issue.rule_name.clone())
386                    .or_default()
387                    .push(issue);
388            }
389
390            // Show limited number of issues per rule type
391            let _max_per_rule = 5;
392            let mut total_shown = 0;
393            let max_total = if self.max_issues_per_file > 0 {
394                self.max_issues_per_file
395            } else {
396                usize::MAX
397            };
398
399            // Sort rule groups by severity (most severe first)
400            let mut sorted_rules: Vec<_> = rule_groups.into_iter().collect();
401            sorted_rules.sort_by(|a, b| {
402                let severity_order = |s: &Severity| match s {
403                    Severity::Nuclear => 3,
404                    Severity::Spicy => 2,
405                    Severity::Mild => 1,
406                };
407                let max_severity_a =
408                    a.1.iter()
409                        .map(|i| severity_order(&i.severity))
410                        .max()
411                        .unwrap_or(1);
412                let max_severity_b =
413                    b.1.iter()
414                        .map(|i| severity_order(&i.severity))
415                        .max()
416                        .unwrap_or(1);
417                max_severity_b.cmp(&max_severity_a)
418            });
419
420            for (rule_name, rule_issues) in sorted_rules {
421                if total_shown >= max_total {
422                    break;
423                }
424
425                let rule_issues_len = rule_issues.len();
426
427                // Create compact summary for each rule type
428                if rule_name.contains("naming") || rule_name.contains("single-letter") {
429                    // Collect variable names for naming issues
430                    let bad_names: Vec<String> = rule_issues
431                        .iter()
432                        .filter_map(|issue| {
433                            if let Some(start) = issue.message.find("'") {
434                                issue.message[start + 1..].find("'").map(|end| issue.message[start + 1..start + 1 + end].to_string())
435                            } else {
436                                None
437                            }
438                        })
439                        .take(5)
440                        .collect();
441
442                    let names_display = if bad_names.len() < rule_issues_len {
443                        format!("{}, ...", bad_names.join(", "))
444                    } else {
445                        bad_names.join(", ")
446                    };
447
448                    let label = if self.i18n.lang == "zh-CN" {
449                        "变量命名问题"
450                    } else {
451                        "Variable naming issues"
452                    };
453
454                    println!(
455                        "  🏷️ {}: {} ({})",
456                        label.bright_yellow().bold(),
457                        rule_issues_len.to_string().bright_red().bold(),
458                        names_display.bright_black()
459                    );
460                    total_shown += 1;
461                } else if rule_name.contains("duplication") {
462                    let label = if self.i18n.lang == "zh-CN" {
463                        "代码重复问题"
464                    } else {
465                        "Code duplication issues"
466                    };
467
468                    // Extract instance count from message if available
469                    let instance_info = if let Some(first_issue) = rule_issues.first() {
470                        if first_issue.message.contains("instances") {
471                            let parts: Vec<&str> = first_issue.message.split_whitespace().collect();
472                            if let Some(pos) = parts.iter().position(|&x| x == "instances") {
473                                if pos > 0 {
474                                    format!("{} instances", parts[pos - 1])
475                                } else {
476                                    "multiple instances".to_string()
477                                }
478                            } else {
479                                "multiple blocks".to_string()
480                            }
481                        } else {
482                            "multiple blocks".to_string()
483                        }
484                    } else {
485                        "multiple blocks".to_string()
486                    };
487
488                    println!(
489                        "  🔄 {}: {} ({})",
490                        label.bright_cyan().bold(),
491                        rule_issues_len.to_string().bright_cyan().bold(),
492                        instance_info.bright_black()
493                    );
494                    total_shown += 1;
495                } else if rule_name.contains("nesting") {
496                    let label = if self.i18n.lang == "zh-CN" {
497                        "嵌套深度问题"
498                    } else {
499                        "Nesting depth issues"
500                    };
501
502                    // Extract depth range from messages
503                    let depths: Vec<usize> = rule_issues
504                        .iter()
505                        .filter_map(|issue| {
506                            if let Some(start) = issue.message.find("depth: ") {
507                                let depth_str = &issue.message[start + 7..];
508                                if let Some(end) = depth_str.find(')') {
509                                    depth_str[..end].parse().ok()
510                                } else {
511                                    None
512                                }
513                            } else if let Some(start) = issue.message.find("深度: ") {
514                                let depth_str = &issue.message[start + 6..];
515                                if let Some(end) = depth_str.find(')') {
516                                    depth_str[..end].parse().ok()
517                                } else {
518                                    None
519                                }
520                            } else {
521                                None
522                            }
523                        })
524                        .collect();
525
526                    let depth_info = if !depths.is_empty() {
527                        let min_depth = depths.iter().min().unwrap_or(&4);
528                        let max_depth = depths.iter().max().unwrap_or(&8);
529                        if min_depth == max_depth {
530                            format!("depth {min_depth}")
531                        } else {
532                            format!("depth {min_depth}-{max_depth}")
533                        }
534                    } else {
535                        "deep nesting".to_string()
536                    };
537
538                    println!(
539                        "  📦 {}: {} ({})",
540                        label.bright_magenta().bold(),
541                        rule_issues_len.to_string().bright_magenta().bold(),
542                        depth_info.bright_black()
543                    );
544                    total_shown += 1;
545                } else {
546                    // For other types, show a generic summary
547                    let clean_rule_name = rule_name.replace("-", " ");
548                    println!(
549                        "  ⚠️ {}: {}",
550                        clean_rule_name.bright_yellow().bold(),
551                        rule_issues_len.to_string().bright_yellow().bold()
552                    );
553                    total_shown += 1;
554                }
555            }
556            println!();
557        }
558    }
559
560    fn print_issue(&self, issue: &CodeIssue) {
561        // Choose icon and color based on rule type
562        if issue.rule_name.contains("duplication") {
563            let message = if self.i18n.lang == "zh-CN" {
564                &issue.message
565            } else {
566                // Translate common duplication messages to English
567                if issue.message.contains("相似代码块") {
568                    "Found similar code blocks, consider refactoring into functions"
569                } else if issue.message.contains("DRY原则哭了") {
570                    "Code duplication detected, DRY principle violated"
571                } else {
572                    &issue.message
573                }
574            };
575            println!(
576                "  {} {} {}",
577                "🔄".bright_cyan(),
578                "duplicate".bright_black(),
579                message.bright_cyan().bold()
580            );
581        } else if issue.rule_name.contains("nesting") {
582            let message = if self.i18n.lang == "zh-CN" {
583                &issue.message
584            } else {
585                // Translate common nesting messages to English
586                if issue.message.contains("俄罗斯套娃") {
587                    "Nesting deeper than Russian dolls, are you writing a maze?"
588                } else if issue.message.contains("挖到地心") {
589                    "Nesting so deep, trying to dig to the Earth's core?"
590                } else if issue.message.contains("像洋葱一样") {
591                    "Code nested like an onion, makes me want to cry"
592                } else {
593                    &issue.message
594                }
595            };
596            println!(
597                "  {} {} {}",
598                "📦".bright_magenta(),
599                "nesting".bright_black(),
600                message.bright_magenta()
601            );
602        } else {
603            // Default based on severity
604            let severity_icon = match issue.severity {
605                Severity::Nuclear => "💥",
606                Severity::Spicy => "🌶️",
607                Severity::Mild => "😐",
608            };
609
610            let line_info = format!("{}:{}", issue.line, issue.column);
611            let colored_message = match issue.severity {
612                Severity::Nuclear => issue.message.red().bold(),
613                Severity::Spicy => issue.message.yellow(),
614                Severity::Mild => issue.message.blue(),
615            };
616
617            let _final_message = if self.savage_mode {
618                self.make_message_savage(&issue.message)
619            } else {
620                issue.message.clone()
621            };
622
623            println!(
624                "  {} {} {}",
625                severity_icon.bright_yellow(),
626                line_info.bright_black(),
627                colored_message
628            );
629        }
630    }
631
632    fn make_message_savage(&self, message: &str) -> String {
633        let savage_prefixes = vec![
634            "🔥 严重警告:",
635            "💀 代码死刑:",
636            "🗑️ 垃圾警报:",
637            "😱 恐怖发现:",
638            "🤮 令人作呕:",
639        ];
640
641        let prefix = savage_prefixes[message.len() % savage_prefixes.len()];
642        format!("{prefix} {message}")
643    }
644
645    fn print_summary_with_score(&self, issues: &[CodeIssue], quality_score: &CodeQualityScore) {
646        // Print detailed scoring breakdown
647        self.print_scoring_breakdown(issues, quality_score);
648        let _nuclear_count = issues
649            .iter()
650            .filter(|i| matches!(i.severity, Severity::Nuclear))
651            .count();
652        let _total_count = issues.len();
653
654        println!("{}", self.i18n.get("summary").bright_white().bold());
655        println!("{}", "─".repeat(50).bright_black());
656
657        // 显示评分总结
658        let score_summary = match quality_score.quality_level {
659            crate::scoring::QualityLevel::Excellent => match self.i18n.lang.as_str() {
660                "zh-CN" => format!(
661                    "🏆 代码质量优秀!评分: {:.1}/100",
662                    quality_score.total_score
663                ),
664                _ => format!(
665                    "🏆 Excellent code quality! Score: {:.1}/100",
666                    quality_score.total_score
667                ),
668            },
669            crate::scoring::QualityLevel::Good => match self.i18n.lang.as_str() {
670                "zh-CN" => format!(
671                    "👍 代码质量良好,评分: {:.1}/100",
672                    quality_score.total_score
673                ),
674                _ => format!(
675                    "👍 Good code quality, Score: {:.1}/100",
676                    quality_score.total_score
677                ),
678            },
679            crate::scoring::QualityLevel::Average => match self.i18n.lang.as_str() {
680                "zh-CN" => format!(
681                    "😐 代码质量一般,评分: {:.1}/100,还有改进空间",
682                    quality_score.total_score
683                ),
684                _ => format!(
685                    "😐 Average code quality, Score: {:.1}/100, room for improvement",
686                    quality_score.total_score
687                ),
688            },
689            crate::scoring::QualityLevel::Poor => match self.i18n.lang.as_str() {
690                "zh-CN" => format!(
691                    "😟 代码质量较差,评分: {:.1}/100,建议重构",
692                    quality_score.total_score
693                ),
694                _ => format!(
695                    "😟 Poor code quality, Score: {:.1}/100, refactoring recommended",
696                    quality_score.total_score
697                ),
698            },
699            crate::scoring::QualityLevel::Terrible => match self.i18n.lang.as_str() {
700                "zh-CN" => format!(
701                    "💀 代码质量糟糕,评分: {:.1}/100,急需重写",
702                    quality_score.total_score
703                ),
704                _ => format!(
705                    "💀 Terrible code quality, Score: {:.1}/100, rewrite urgently needed",
706                    quality_score.total_score
707                ),
708            },
709        };
710
711        let score_color = match quality_score.quality_level {
712            crate::scoring::QualityLevel::Excellent => score_summary.bright_green().bold(),
713            crate::scoring::QualityLevel::Good => score_summary.green(),
714            crate::scoring::QualityLevel::Average => score_summary.yellow(),
715            crate::scoring::QualityLevel::Poor => score_summary.red(),
716            crate::scoring::QualityLevel::Terrible => score_summary.bright_red().bold(),
717        };
718
719        println!("{score_color}");
720        println!();
721
722        let nuclear_count = issues
723            .iter()
724            .filter(|i| matches!(i.severity, Severity::Nuclear))
725            .count();
726        let total_count = issues.len();
727
728        let summary_message = if nuclear_count > 0 {
729            if self.savage_mode {
730                match self.i18n.lang.as_str() {
731                    "zh-CN" => "你的代码质量堪忧,建议重新学习编程基础 💀".to_string(),
732                    _ => "Your code quality is concerning, suggest learning programming basics again 💀".to_string(),
733                }
734            } else {
735                match self.i18n.lang.as_str() {
736                    "zh-CN" => "发现了一些严重问题,建议优先修复核弹级问题 🔥".to_string(),
737                    _ => "Found some serious issues, suggest fixing nuclear problems first 🔥"
738                        .to_string(),
739                }
740            }
741        } else if total_count > 10 {
742            match self.i18n.lang.as_str() {
743                "zh-CN" => "问题有点多,建议分批修复 📝".to_string(),
744                _ => "Quite a few issues, suggest fixing them in batches 📝".to_string(),
745            }
746        } else {
747            match self.i18n.lang.as_str() {
748                "zh-CN" => "问题不多,稍微改进一下就好了 👍".to_string(),
749                _ => "Not many issues, just need some minor improvements 👍".to_string(),
750            }
751        };
752
753        let color = if nuclear_count > 0 {
754            summary_message.red().bold()
755        } else if total_count > 10 {
756            summary_message.yellow()
757        } else {
758            summary_message.green()
759        };
760
761        println!("{color}");
762    }
763
764    fn print_scoring_breakdown(&self, _issues: &[CodeIssue], quality_score: &CodeQualityScore) {
765        let title = if self.i18n.lang == "zh-CN" {
766            "📊 评分详情"
767        } else {
768            "📊 Scoring Details"
769        };
770
771        println!("\n{}", title.bright_cyan().bold());
772        println!("{}", "─".repeat(50).bright_black());
773
774        // Show category scores
775        self.print_category_scores(&quality_score.category_scores);
776
777        // Show weighted calculation
778        self.print_weighted_calculation(&quality_score.category_scores, quality_score.total_score);
779
780        // Show scoring scale
781        let scale_title = if self.i18n.lang == "zh-CN" {
782            "\n📏 评分标准 (分数越高代码越烂):"
783        } else {
784            "\n📏 Scoring Scale (higher score = worse code):"
785        };
786
787        println!("{}", scale_title.bright_yellow());
788        if self.i18n.lang == "zh-CN" {
789            println!("  💀 81-100: 糟糕    🔥 61-80: 较差    ⚠️ 41-60: 一般");
790            println!("  ✅ 21-40: 良好     🌟 0-20: 优秀");
791        } else {
792            println!("  💀 81-100: Terrible    🔥 61-80: Poor    ⚠️ 41-60: Average");
793            println!("  ✅ 21-40: Good         🌟 0-20: Excellent");
794        }
795    }
796
797    fn calculate_base_score_for_display(
798        &self,
799        issues: &[CodeIssue],
800        scorer: &crate::scoring::CodeScorer,
801    ) -> f64 {
802        let mut score = 0.0;
803        for issue in issues {
804            let rule_weight = scorer.rule_weights.get(&issue.rule_name).unwrap_or(&1.0);
805            let severity_weight = match issue.severity {
806                crate::analyzer::Severity::Nuclear => 10.0,
807                crate::analyzer::Severity::Spicy => 5.0,
808                crate::analyzer::Severity::Mild => 2.0,
809            };
810            score += rule_weight * severity_weight;
811        }
812        score
813    }
814
815    fn calculate_density_penalty_for_display(
816        &self,
817        issue_count: usize,
818        file_count: usize,
819        total_lines: usize,
820    ) -> f64 {
821        if total_lines == 0 || file_count == 0 {
822            return 0.0;
823        }
824
825        let issues_per_1000_lines = (issue_count as f64 / total_lines as f64) * 1000.0;
826        let issues_per_file = issue_count as f64 / file_count as f64;
827
828        let density_penalty = match issues_per_1000_lines {
829            x if x > 50.0 => 25.0,
830            x if x > 30.0 => 15.0,
831            x if x > 20.0 => 10.0,
832            x if x > 10.0 => 5.0,
833            _ => 0.0,
834        };
835
836        let file_penalty = match issues_per_file {
837            x if x > 20.0 => 15.0,
838            x if x > 10.0 => 10.0,
839            x if x > 5.0 => 5.0,
840            _ => 0.0,
841        };
842
843        density_penalty + file_penalty
844    }
845
846    fn calculate_severity_penalty_for_display(
847        &self,
848        distribution: &crate::scoring::SeverityDistribution,
849    ) -> f64 {
850        let mut penalty = 0.0;
851
852        if distribution.nuclear > 0 {
853            penalty += 20.0 + (distribution.nuclear as f64 - 1.0) * 5.0;
854        }
855
856        if distribution.spicy > 5 {
857            penalty += (distribution.spicy as f64 - 5.0) * 2.0;
858        }
859
860        if distribution.mild > 20 {
861            penalty += (distribution.mild as f64 - 20.0) * 0.5;
862        }
863
864        penalty
865    }
866
867    fn print_category_scores(&self, category_scores: &std::collections::HashMap<String, f64>) {
868        let title = if self.i18n.lang == "zh-CN" {
869            "📋 分类评分详情:"
870        } else {
871            "📋 Category Scores:"
872        };
873
874        println!("{}", title.bright_yellow());
875
876        // Define category display order and info
877        let categories = [
878            ("naming", "命名规范", "Naming", "🏷️"),
879            ("complexity", "复杂度", "Complexity", "🧩"),
880            ("duplication", "代码重复", "Duplication", "🔄"),
881            ("rust-basics", "Rust基础", "Rust Basics", "🦀"),
882            ("advanced-rust", "高级特性", "Advanced Rust", "⚡"),
883            ("rust-features", "Rust功能", "Rust Features", "🚀"),
884            ("structure", "代码结构", "Code Structure", "🏗️"),
885        ];
886
887        for (category_key, zh_name, en_name, icon) in &categories {
888            if let Some(score) = category_scores.get(*category_key) {
889                let display_name = if self.i18n.lang == "zh-CN" {
890                    zh_name
891                } else {
892                    en_name
893                };
894                let (status_icon, status_text) = self.get_score_status(*score);
895
896                // basic display
897                println!(
898                    "  {} {} {}分     {}",
899                    status_icon,
900                    format!("{icon} {display_name}").bright_white(),
901                    format!("{score:.0}").bright_cyan(),
902                    status_text.bright_black()
903                );
904
905                // if score is high (code is bad), add a roast
906                if let Some(roast) = self.get_category_roast(category_key, *score) {
907                    println!("    💬 {}", roast.bright_yellow().italic());
908                }
909            }
910        }
911        println!();
912    }
913
914    fn get_score_status(&self, score: f64) -> (&str, &str) {
915        // 注意:分数越高代码越烂
916        match score as u32 {
917            81..=100 => (
918                "⚠",
919                if self.i18n.lang == "zh-CN" {
920                    "糟糕,急需修复"
921                } else {
922                    "Terrible, urgent fixes needed"
923                },
924            ),
925            61..=80 => (
926                "•",
927                if self.i18n.lang == "zh-CN" {
928                    "较差,建议重构"
929                } else {
930                    "Poor, refactoring recommended"
931                },
932            ),
933            41..=60 => (
934                "○",
935                if self.i18n.lang == "zh-CN" {
936                    "一般,需要改进"
937                } else {
938                    "Average, needs improvement"
939                },
940            ),
941            21..=40 => (
942                "✓",
943                if self.i18n.lang == "zh-CN" {
944                    "良好,还有提升空间"
945                } else {
946                    "Good, room for improvement"
947                },
948            ),
949            _ => (
950                "✓✓",
951                if self.i18n.lang == "zh-CN" {
952                    "优秀,继续保持"
953                } else {
954                    "Excellent, keep it up"
955                },
956            ),
957        }
958    }
959
960    fn get_category_roast(&self, category: &str, score: f64) -> Option<String> {
961        // only roast if score is high (code is bad)
962        if score < 60.0 {
963            return None;
964        }
965
966        let roasts = match (self.i18n.lang.as_str(), category) {
967            ("zh-CN", "naming") => vec![
968                "变量命名比我的编程技能还要抽象 🤔",
969                "这些变量名让维护者想哭着辞职 😭",
970                "变量名的创意程度约等于给孩子起名叫'小明' 🙄",
971                "恭喜!你成功让变量名比注释还难懂 🏆",
972            ],
973            ("zh-CN", "complexity") => vec![
974                "这复杂度比俄罗斯套娃还要深 🪆",
975                "代码复杂得像洋葱一样,剥一层哭一次 🧅",
976                "这函数比我的人际关系还复杂 😵‍💫",
977                "复杂度爆表!连AI都看不懂了 🤖",
978            ],
979            ("zh-CN", "duplication") => vec![
980                "检测到重复代码!你是复制粘贴大师吗? 🥷",
981                "DRY原则哭了,你的代码湿得像雨季 🌧️",
982                "这些重复代码比双胞胎还像 👯‍♀️",
983                "建议改名为copy-paste.rs 📋",
984            ],
985            ("zh-CN", "rust-features") => vec![
986                "宏定义比我的借口还多 🎭",
987                "这么多宏,IDE都要罢工了 💻",
988                "宏滥用!编译时间都被你搞长了 ⏰",
989            ],
990            ("en-US", "naming") => vec![
991                "Variable names more abstract than modern art 🎨",
992                "These names make maintainers want to quit and sell hotdogs 🌭",
993                "Variable naming creativity level: calling a kid 'Child' 👶",
994                "Congrats! Variables harder to understand than comments 🏆",
995            ],
996            ("en-US", "complexity") => vec![
997                "Complexity deeper than Russian dolls 🪆",
998                "Code nested like an onion, peel one layer, cry once 🧅",
999                "This function is more complex than my relationships 😵‍💫",
1000                "Complexity off the charts! Even AI gave up 🤖",
1001            ],
1002            ("en-US", "duplication") => vec![
1003                "Copy-paste ninja detected! 🥷",
1004                "DRY principle crying while your code drowns in repetition 🌧️",
1005                "More duplicates than a hall of mirrors 🪞",
1006                "Suggest renaming to ctrl-c-ctrl-v.rs 📋",
1007            ],
1008            ("en-US", "rust-features") => vec![
1009                "More macros than my excuses 🎭",
1010                "So many macros, even the IDE wants to quit 💻",
1011                "Macro abuse! Compile time extended indefinitely ⏰",
1012            ],
1013            _ => vec![],
1014        };
1015
1016        if roasts.is_empty() {
1017            None
1018        } else {
1019            // select roast based on score (the higher the score, the more severe the roast)
1020            let index = ((score - 60.0) / 10.0) as usize;
1021            let roast_index = index.min(roasts.len() - 1);
1022            Some(roasts[roast_index].to_string())
1023        }
1024    }
1025
1026    fn print_weighted_calculation(
1027        &self,
1028        category_scores: &std::collections::HashMap<String, f64>,
1029        _total_score: f64,
1030    ) {
1031        let calc_title = if self.i18n.lang == "zh-CN" {
1032            "🧮 加权计算:"
1033        } else {
1034            "🧮 Weighted Calculation:"
1035        };
1036
1037        println!("{}", calc_title.bright_yellow());
1038
1039        // Show the calculation formula
1040        let weights = [
1041            ("naming", 0.25, "命名规范", "Naming"),
1042            ("complexity", 0.20, "复杂度", "Complexity"),
1043            ("duplication", 0.15, "代码重复", "Duplication"),
1044            ("rust-basics", 0.15, "Rust基础", "Rust Basics"),
1045            ("advanced-rust", 0.10, "高级特性", "Advanced Rust"),
1046            ("rust-features", 0.10, "Rust功能", "Rust Features"),
1047            ("structure", 0.05, "代码结构", "Code Structure"),
1048        ];
1049
1050        let mut calculation_parts = Vec::new();
1051        let mut weighted_sum = 0.0;
1052
1053        for (category_key, weight, _zh_name, _en_name) in &weights {
1054            if let Some(score) = category_scores.get(*category_key) {
1055                let weighted_value = score * weight;
1056                weighted_sum += weighted_value;
1057                calculation_parts.push(format!("{score:.1}×{weight:.2}"));
1058            }
1059        }
1060
1061        if self.i18n.lang == "zh-CN" {
1062            println!(
1063                "  评分计算: ({}) ÷ 1.00 = {}",
1064                calculation_parts.join(" + ").bright_white(),
1065                format!("{weighted_sum:.1}").bright_green().bold()
1066            );
1067        } else {
1068            println!(
1069                "  Score calculation: ({}) ÷ 1.00 = {}",
1070                calculation_parts.join(" + ").bright_white(),
1071                format!("{weighted_sum:.1}").bright_green().bold()
1072            );
1073        }
1074    }
1075
1076    fn print_detailed_base_score_breakdown(
1077        &self,
1078        issues: &[CodeIssue],
1079        scorer: &crate::scoring::CodeScorer,
1080    ) {
1081        // Group issues by rule type and calculate scores
1082        let mut rule_scores: std::collections::HashMap<String, (usize, f64)> =
1083            std::collections::HashMap::new();
1084
1085        for issue in issues {
1086            let rule_weight = scorer.rule_weights.get(&issue.rule_name).unwrap_or(&1.0);
1087            let severity_weight = match issue.severity {
1088                crate::analyzer::Severity::Nuclear => 10.0,
1089                crate::analyzer::Severity::Spicy => 5.0,
1090                crate::analyzer::Severity::Mild => 2.0,
1091            };
1092            let issue_score = rule_weight * severity_weight;
1093
1094            let entry = rule_scores
1095                .entry(issue.rule_name.clone())
1096                .or_insert((0, 0.0));
1097            entry.0 += 1; // count
1098            entry.1 += issue_score; // total score
1099        }
1100
1101        // Sort by score (highest first)
1102        let mut sorted_rules: Vec<_> = rule_scores.into_iter().collect();
1103        sorted_rules.sort_by(|a, b| {
1104            b.1 .1
1105                .partial_cmp(&a.1 .1)
1106                .unwrap_or(std::cmp::Ordering::Equal)
1107        });
1108
1109        let breakdown_title = if self.i18n.lang == "zh-CN" {
1110            "🔍 基础分数详细计算:"
1111        } else {
1112            "🔍 Base score detailed calculation:"
1113        };
1114
1115        println!("{}", breakdown_title.bright_yellow());
1116
1117        for (rule_name, (count, total_score)) in sorted_rules.iter().take(10) {
1118            let rule_weight = scorer.rule_weights.get(rule_name).unwrap_or(&1.0);
1119
1120            let rule_display = match (self.i18n.lang.as_str(), rule_name.as_str()) {
1121                ("zh-CN", "terrible-naming") => "糟糕命名",
1122                ("zh-CN", "single-letter-variable") => "单字母变量",
1123                ("zh-CN", "deep-nesting") => "深度嵌套",
1124                ("zh-CN", "code-duplication") => "代码重复",
1125                ("zh-CN", "long-function") => "超长函数",
1126                ("zh-CN", "macro-abuse") => "宏滥用",
1127                (_, "terrible-naming") => "Terrible naming",
1128                (_, "single-letter-variable") => "Single letter vars",
1129                (_, "deep-nesting") => "Deep nesting",
1130                (_, "code-duplication") => "Code duplication",
1131                (_, "long-function") => "Long function",
1132                (_, "macro-abuse") => "Macro abuse",
1133                _ => rule_name,
1134            };
1135
1136            if self.i18n.lang == "zh-CN" {
1137                println!(
1138                    "  • {} × {} (权重{:.1}) = {}",
1139                    format!("{count}").cyan(),
1140                    rule_display.bright_white(),
1141                    format!("{rule_weight:.1}").yellow(),
1142                    format!("{total_score:.1}").bright_red()
1143                );
1144            } else {
1145                println!(
1146                    "  • {} × {} (weight {:.1}) = {}",
1147                    format!("{count}").cyan(),
1148                    rule_display.bright_white(),
1149                    format!("{rule_weight:.1}").yellow(),
1150                    format!("{total_score:.1}").bright_red()
1151                );
1152            }
1153        }
1154        println!();
1155    }
1156
1157    fn print_footer(&self, issues: &[CodeIssue]) {
1158        println!();
1159        println!("{}", self.i18n.get("suggestions").bright_cyan().bold());
1160        println!("{}", "─".repeat(50).bright_black());
1161
1162        let rule_names: Vec<String> = issues
1163            .iter()
1164            .map(|issue| issue.rule_name.clone())
1165            .collect::<std::collections::HashSet<_>>()
1166            .into_iter()
1167            .collect();
1168
1169        let suggestions = self.i18n.get_suggestions(&rule_names);
1170        for suggestion in suggestions {
1171            println!("   {}", suggestion.cyan());
1172        }
1173
1174        println!();
1175        let footer_message = if self.savage_mode {
1176            match self.i18n.lang.as_str() {
1177                "zh-CN" => "记住:写垃圾代码容易,写好代码需要用心 💪".to_string(),
1178                _ => "Remember: writing garbage code is easy, writing good code requires effort 💪"
1179                    .to_string(),
1180            }
1181        } else {
1182            self.i18n.get("keep_improving")
1183        };
1184
1185        let color = if self.savage_mode {
1186            footer_message.bright_red().bold()
1187        } else {
1188            footer_message.bright_green().bold()
1189        };
1190
1191        println!("{color}");
1192    }
1193
1194    fn print_top_files(&self, issues: &[CodeIssue]) {
1195        if self.top_files == 0 {
1196            return;
1197        }
1198
1199        let mut file_issue_counts: HashMap<String, usize> = HashMap::new();
1200        for issue in issues {
1201            let file_name = issue
1202                .file_path
1203                .file_name()
1204                .unwrap_or_default()
1205                .to_string_lossy()
1206                .to_string();
1207            *file_issue_counts.entry(file_name).or_insert(0) += 1;
1208        }
1209
1210        let mut sorted_files: Vec<_> = file_issue_counts.into_iter().collect();
1211        sorted_files.sort_by(|a, b| b.1.cmp(&a.1));
1212
1213        if !sorted_files.is_empty() {
1214            println!("{}", self.i18n.get("top_files").bright_yellow().bold());
1215            println!("{}", "─".repeat(50).bright_black());
1216
1217            for (i, (file_name, count)) in sorted_files.iter().take(self.top_files).enumerate() {
1218                let rank = format!("{}.", i + 1);
1219                println!(
1220                    "   {} {} ({} issues)",
1221                    rank.bright_white(),
1222                    file_name.bright_blue(),
1223                    count.to_string().red()
1224                );
1225            }
1226            println!();
1227        }
1228    }
1229
1230    fn print_detailed_analysis(&self, issues: &[CodeIssue]) {
1231        println!(
1232            "{}",
1233            self.i18n.get("detailed_analysis").bright_magenta().bold()
1234        );
1235        println!("{}", "─".repeat(50).bright_black());
1236
1237        let mut rule_stats: HashMap<String, usize> = HashMap::new();
1238        for issue in issues {
1239            *rule_stats.entry(issue.rule_name.clone()).or_insert(0) += 1;
1240        }
1241
1242        let rule_descriptions = match self.i18n.lang.as_str() {
1243            "zh-CN" => [
1244                ("terrible-naming", "糟糕的变量命名"),
1245                ("single-letter-variable", "单字母变量"),
1246                ("deep-nesting", "过度嵌套"),
1247                ("long-function", "超长函数"),
1248                ("unwrap-abuse", "unwrap() 滥用"),
1249                ("unnecessary-clone", "不必要的 clone()"),
1250            ]
1251            .iter()
1252            .cloned()
1253            .collect::<HashMap<_, _>>(),
1254            _ => [
1255                ("terrible-naming", "Terrible variable naming"),
1256                ("single-letter-variable", "Single letter variables"),
1257                ("deep-nesting", "Deep nesting"),
1258                ("long-function", "Long functions"),
1259                ("unwrap-abuse", "unwrap() abuse"),
1260                ("unnecessary-clone", "Unnecessary clone()"),
1261            ]
1262            .iter()
1263            .cloned()
1264            .collect::<HashMap<_, _>>(),
1265        };
1266
1267        for (rule_name, count) in rule_stats {
1268            let rule_name_str = rule_name.as_str();
1269            let description = rule_descriptions
1270                .get(rule_name_str)
1271                .unwrap_or(&rule_name_str);
1272            println!(
1273                "   📌 {}: {} issues",
1274                description.cyan(),
1275                count.to_string().yellow()
1276            );
1277        }
1278        println!();
1279    }
1280
1281    fn print_markdown_report(&self, issues: &[CodeIssue]) {
1282        let total = issues.len();
1283        let nuclear = issues
1284            .iter()
1285            .filter(|i| matches!(i.severity, Severity::Nuclear))
1286            .count();
1287        let spicy = issues
1288            .iter()
1289            .filter(|i| matches!(i.severity, Severity::Spicy))
1290            .count();
1291        let mild = issues
1292            .iter()
1293            .filter(|i| matches!(i.severity, Severity::Mild))
1294            .count();
1295
1296        println!("# {}", self.i18n.get("title"));
1297        println!();
1298        println!("## {}", self.i18n.get("statistics"));
1299        println!();
1300        println!("| Severity | Count | Description |");
1301        println!("| --- | --- | --- |");
1302        println!(
1303            "| 🔥 Nuclear | {} | {} |",
1304            nuclear,
1305            self.i18n.get("nuclear_issues")
1306        );
1307        println!(
1308            "| 🌶️ Spicy | {} | {} |",
1309            spicy,
1310            self.i18n.get("spicy_issues")
1311        );
1312        println!("| 😐 Mild | {} | {} |", mild, self.i18n.get("mild_issues"));
1313        println!(
1314            "| **Total** | **{}** | **{}** |",
1315            total,
1316            self.i18n.get("total")
1317        );
1318        println!();
1319
1320        if self.verbose {
1321            println!("## {}", self.i18n.get("detailed_analysis"));
1322            println!();
1323
1324            let mut rule_stats: HashMap<String, usize> = HashMap::new();
1325            for issue in issues {
1326                *rule_stats.entry(issue.rule_name.clone()).or_insert(0) += 1;
1327            }
1328
1329            for (rule_name, count) in rule_stats {
1330                println!("- **{}**: {} issues", rule_name, count);
1331            }
1332            println!();
1333        }
1334
1335        println!("## Issues by File");
1336        println!();
1337
1338        let mut file_groups: HashMap<String, Vec<&CodeIssue>> = HashMap::new();
1339        for issue in issues {
1340            let file_name = issue
1341                .file_path
1342                .file_name()
1343                .unwrap_or_default()
1344                .to_string_lossy()
1345                .to_string();
1346            file_groups.entry(file_name).or_default().push(issue);
1347        }
1348
1349        for (file_name, file_issues) in file_groups {
1350            println!("### 📁 {}", file_name);
1351            println!();
1352
1353            let issues_to_show = if self.max_issues_per_file > 0 {
1354                file_issues
1355                    .into_iter()
1356                    .take(self.max_issues_per_file)
1357                    .collect::<Vec<_>>()
1358            } else {
1359                file_issues
1360            };
1361
1362            for issue in issues_to_show {
1363                let severity_icon = match issue.severity {
1364                    Severity::Nuclear => "💥",
1365                    Severity::Spicy => "🌶️",
1366                    Severity::Mild => "😐",
1367                };
1368
1369                let messages = self.i18n.get_roast_messages(&issue.rule_name);
1370                let message = if !messages.is_empty() {
1371                    messages[issue.line % messages.len()].clone()
1372                } else {
1373                    issue.message.clone()
1374                };
1375
1376                println!(
1377                    "- {} **Line {}:{}** - {}",
1378                    severity_icon, issue.line, issue.column, message
1379                );
1380            }
1381            println!();
1382        }
1383
1384        println!("## {}", self.i18n.get("suggestions"));
1385        println!();
1386
1387        let rule_names: Vec<String> = issues
1388            .iter()
1389            .map(|issue| issue.rule_name.clone())
1390            .collect::<std::collections::HashSet<_>>()
1391            .into_iter()
1392            .collect();
1393
1394        let suggestions = self.i18n.get_suggestions(&rule_names);
1395        for suggestion in suggestions {
1396            println!("- {}", suggestion);
1397        }
1398    }
1399}