1use crate::complexity::EntropyAnalysis;
2use crate::priority::UnifiedDebtItem;
3use colored::*;
4use std::fmt::Write;
5
6pub fn classify_dependency_contribution(dependency_factor: f64) -> &'static str {
8 match dependency_factor {
9 d if d > 10.0 => "CRITICAL PATH",
10 d if d > 5.0 => "HIGH",
11 d if d > 2.0 => "MEDIUM",
12 _ => "LOW",
13 }
14}
15
16pub fn format_callers_display(callers: &[String], max_display: usize) -> String {
18 if callers.len() <= max_display {
19 callers.join(", ")
20 } else {
21 format!(
22 "{}, ... ({} more)",
23 callers[..max_display].join(", "),
24 callers.len() - max_display
25 )
26 }
27}
28
29pub fn format_callees_display(callees: &[String], max_display: usize) -> String {
31 if callees.len() <= max_display {
32 callees.join(", ")
33 } else {
34 format!(
35 "{}, ... ({} more)",
36 callees[..max_display].join(", "),
37 callees.len() - max_display
38 )
39 }
40}
41
42pub struct ScoreFactors {
44 pub coverage_gap: f64,
45 pub coverage_pct: f64,
46 pub coverage_factor: f64,
47 pub complexity_factor: f64,
48 pub dependency_factor: f64,
49}
50
51pub fn calculate_score_factors(item: &UnifiedDebtItem) -> ScoreFactors {
52 let (coverage_gap, coverage_pct) = if let Some(ref trans_cov) = item.transitive_coverage {
53 let pct = trans_cov.direct;
54 (1.0 - pct, pct)
55 } else {
56 (1.0, 0.0)
57 };
58
59 ScoreFactors {
60 coverage_gap,
61 coverage_pct,
62 coverage_factor: (coverage_gap.powf(1.5) + 0.1).max(0.1),
63 complexity_factor: item.unified_score.complexity_factor,
64 dependency_factor: ((item.unified_score.dependency_factor + 1.0).sqrt() / 2.0).min(1.0),
65 }
66}
67
68pub fn format_coverage_detail(has_coverage: bool, gap: f64, pct: f64) -> String {
70 if has_coverage {
71 format!(" (gap: {:.1}%, coverage: {:.1}%)", gap * 100.0, pct * 100.0)
72 } else {
73 " (no coverage data)".to_string()
74 }
75}
76
77pub fn format_complexity_detail(entropy: &Option<EntropyAnalysis>) -> String {
79 if let Some(e) = entropy {
80 format!(" (entropy-adjusted from {})", e.original_complexity)
81 } else {
82 String::new()
83 }
84}
85
86pub fn format_score_calculation_section(
88 item: &UnifiedDebtItem,
89 _formatter: &crate::formatting::ColoredFormatter,
90) -> Vec<String> {
91 let mut lines = Vec::new();
92 let tree_branch = "-";
93 let tree_sub_branch = " -";
94 let tree_pipe = " ";
95
96 lines.push(format!(
97 "{} {}",
98 tree_branch,
99 "SCORE CALCULATION:".bright_blue()
100 ));
101 lines.push(format!("{} Weighted Sum Model:", tree_sub_branch));
102
103 let factors = calculate_score_factors(item);
105 let coverage_detail = format_coverage_detail(
106 item.transitive_coverage.is_some(),
107 factors.coverage_gap,
108 factors.coverage_pct,
109 );
110
111 let role_coverage_indicator = if matches!(
113 item.function_role,
114 crate::priority::FunctionRole::EntryPoint
115 ) {
116 " (entry point - integration tested, lower unit coverage expected)"
117 } else {
118 ""
119 };
120
121 lines.push(format!(
122 "{} {} Coverage Score: {:.1} × 40% = {:.2}{}{}",
123 tree_pipe,
124 "-",
125 factors.coverage_factor * 10.0, factors.coverage_factor * 10.0 * 0.4,
127 coverage_detail,
128 role_coverage_indicator
129 ));
130
131 let complexity_detail = format_complexity_detail(&item.entropy_analysis);
133 lines.push(format!(
134 "{} {} Complexity Score: {:.1} × 40% = {:.2}{}",
135 tree_pipe,
136 "-",
137 factors.complexity_factor * 10.0, factors.complexity_factor * 10.0 * 0.40,
139 complexity_detail
140 ));
141
142 lines.push(format!(
144 "{} {} Dependency Score: {:.1} × 20% = {:.2} ({} callers)",
145 tree_pipe,
146 "-",
147 factors.dependency_factor * 10.0, factors.dependency_factor * 10.0 * 0.20,
149 item.upstream_callers.len() ));
151
152 let coverage_contribution = factors.coverage_factor * 10.0 * 0.4;
154 let complexity_contribution = factors.complexity_factor * 10.0 * 0.4;
155 let dependency_contribution = factors.dependency_factor * 10.0 * 0.2;
156 let base_score = coverage_contribution + complexity_contribution + dependency_contribution;
157
158 lines.push(format!(
159 "{} {} Base Score: {:.2} + {:.2} + {:.2} = {:.2}",
160 tree_pipe,
161 "-",
162 coverage_contribution,
163 complexity_contribution,
164 dependency_contribution,
165 base_score
166 ));
167
168 if let Some(ref entropy) = item.entropy_analysis {
170 lines.push(format!(
171 "{} {} Entropy Impact: {:.0}% dampening (entropy: {:.2}, repetition: {:.0}%)",
172 tree_pipe,
173 "-",
174 (1.0 - entropy.dampening_factor) * 100.0,
175 entropy.entropy_score,
176 entropy.pattern_repetition * 100.0
177 ));
178 }
179
180 lines.push(format!(
181 "{} {} Role Adjustment: ×{:.2}",
182 tree_pipe, "-", item.unified_score.role_multiplier
183 ));
184
185 lines.push(format!(
186 "{} {} Final Score: {:.2}",
187 tree_pipe, "-", item.unified_score.final_score
188 ));
189
190 lines
191}
192
193pub fn format_call_graph_section(
195 item: &UnifiedDebtItem,
196 _formatter: &crate::formatting::ColoredFormatter,
197) -> Vec<String> {
198 let mut lines = Vec::new();
199 let tree_pipe = " ";
200
201 if !item.upstream_callers.is_empty() || !item.downstream_callees.is_empty() {
202 lines.push(format!("{} {}", "-", "CALL GRAPH:".bright_blue()));
203
204 if !item.upstream_callers.is_empty() {
205 let callers = format_callers_display(&item.upstream_callers, 5);
206 lines.push(format!("{} {} Called by: {}", tree_pipe, "-", callers));
207 }
208
209 if !item.downstream_callees.is_empty() {
210 let callees = format_callees_display(&item.downstream_callees, 5);
211 lines.push(format!("{} {} Calls: {}", tree_pipe, "-", callees));
212 } else if !item.upstream_callers.is_empty() {
213 lines.push(format!(
215 "{} {} Dependencies: {} upstream, {} downstream",
216 tree_pipe, "-", item.upstream_dependencies, item.downstream_dependencies
217 ));
218 }
219 }
220
221 lines
222}
223
224pub fn format_basic_call_graph(
226 output: &mut String,
227 item: &UnifiedDebtItem,
228 _formatter: &crate::formatting::ColoredFormatter,
229) {
230 let caller_count = item.upstream_callers.len();
231 let callee_count = item.downstream_callees.len();
232
233 if caller_count > 0 || callee_count > 0 {
235 writeln!(
236 output,
237 "- {} {} caller{}, {} callee{}",
238 "CALLS:".bright_blue(),
239 caller_count,
240 if caller_count == 1 { "" } else { "s" },
241 callee_count,
242 if callee_count == 1 { "" } else { "s" }
243 )
244 .unwrap();
245
246 if caller_count == 0 && callee_count > 0 {
248 writeln!(
249 output,
250 " ! {}",
251 "No callers detected - may be dead code".yellow()
252 )
253 .unwrap();
254 }
255 }
256}
257
258pub fn format_implementation_steps(
260 output: &mut String,
261 steps: &[String],
262 _formatter: &crate::formatting::ColoredFormatter,
263) {
264 if !steps.is_empty() {
265 for (i, step) in steps.iter().enumerate() {
266 let prefix = " -";
267 writeln!(
268 output,
269 "{} {}. {}",
270 prefix,
271 (i + 1).to_string().cyan(),
272 step.bright_white()
273 )
274 .unwrap();
275 }
276 }
277}
278
279pub fn format_dependencies_summary(
281 output: &mut String,
282 item: &UnifiedDebtItem,
283 _formatter: &crate::formatting::ColoredFormatter,
284 tree_pipe: &str,
285) {
286 let (upstream, downstream) = crate::priority::formatter::extract_dependency_info(item);
287
288 if upstream > 0 || downstream > 0 {
289 writeln!(
290 output,
291 "- {} {} upstream, {} downstream",
292 "DEPENDENCIES:".bright_blue(),
293 upstream.to_string().cyan(),
294 downstream.to_string().cyan()
295 )
296 .unwrap();
297
298 if !item.upstream_callers.is_empty() {
299 let callers_display = format_callers_display(&item.upstream_callers, 3);
300 writeln!(
301 output,
302 "{} - CALLERS: {}",
303 tree_pipe,
304 callers_display.cyan()
305 )
306 .unwrap();
307 }
308
309 if !item.downstream_callees.is_empty() {
310 let callees_display = format_callees_display(&item.downstream_callees, 3);
311 writeln!(
312 output,
313 "{} - CALLS: {}",
314 tree_pipe,
315 callees_display.bright_magenta()
316 )
317 .unwrap();
318 }
319 }
320}
321
322pub fn format_scoring_breakdown(
324 output: &mut String,
325 item: &UnifiedDebtItem,
326 _formatter: &crate::formatting::ColoredFormatter,
327) {
328 use super::complexity::classify_complexity_contribution;
329 use super::coverage::classify_coverage_contribution;
330
331 let coverage_contribution = classify_coverage_contribution(item);
332 let complexity_contribution =
333 classify_complexity_contribution(item.unified_score.complexity_factor);
334 let dependency_contribution =
335 classify_dependency_contribution(item.unified_score.dependency_factor);
336
337 writeln!(
338 output,
339 "- {} Coverage: {} | Complexity: {} | Dependencies: {}",
340 "SCORING:".bright_blue(),
341 coverage_contribution.bright_yellow(),
342 complexity_contribution.bright_yellow(),
343 dependency_contribution.bright_yellow()
344 )
345 .unwrap();
346
347 if let Some(ref context) = item.file_context {
349 use crate::priority::scoring::file_context_scoring::{
350 context_label, context_reduction_factor,
351 };
352
353 let factor = context_reduction_factor(context);
354 let label = context_label(context);
355
356 let explanation = if factor >= 1.0 {
358 "no score adjustment"
359 } else if factor >= 0.6 {
360 "40% score reduction"
361 } else if factor >= 0.2 {
362 "80% score reduction"
363 } else {
364 "90% score reduction"
365 };
366
367 writeln!(
368 output,
369 " - {} {} ({})",
370 "File Context:".bright_blue(),
371 label.bright_magenta(),
372 explanation
373 )
374 .unwrap();
375
376 writeln!(
377 output,
378 " - {} {:.2}",
379 "Context Factor:".bright_blue(),
380 factor
381 )
382 .unwrap();
383 }
384}
385
386pub fn format_related_items(
388 output: &mut String,
389 related_items: &[String],
390 _formatter: &crate::formatting::ColoredFormatter,
391) {
392 if !related_items.is_empty() {
393 writeln!(
394 output,
395 "- {} {} related items to address:",
396 "RELATED:".bright_blue(),
397 related_items.len().to_string().cyan()
398 )
399 .unwrap();
400
401 for related in related_items.iter() {
402 let prefix = " -";
403 writeln!(output, "{} {}", prefix, related.bright_magenta()).unwrap();
404 }
405 }
406}
407
408pub fn format_pattern_analysis(output: &mut String, item: &UnifiedDebtItem, verbosity: u8) {
410 if verbosity < 1 {
412 return;
413 }
414
415 if let Some(ref pattern_analysis) = item.pattern_analysis
416 && pattern_analysis.has_patterns()
417 {
418 writeln!(output, "├─ {}", "PATTERN ANALYSIS:".bright_blue()).unwrap();
419
420 let formatted =
422 crate::output::pattern_formatter::PatternFormatter::format(pattern_analysis);
423
424 for line in formatted.lines() {
426 if !line.is_empty() {
427 writeln!(output, "│ {}", line).unwrap();
428 }
429 }
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn test_classify_dependency_contribution() {
439 assert_eq!(classify_dependency_contribution(15.0), "CRITICAL PATH");
440 assert_eq!(classify_dependency_contribution(10.1), "CRITICAL PATH");
441 assert_eq!(classify_dependency_contribution(10.0), "HIGH");
442 assert_eq!(classify_dependency_contribution(7.0), "HIGH");
443 assert_eq!(classify_dependency_contribution(5.1), "HIGH");
444 assert_eq!(classify_dependency_contribution(5.0), "MEDIUM");
445 assert_eq!(classify_dependency_contribution(3.0), "MEDIUM");
446 assert_eq!(classify_dependency_contribution(2.1), "MEDIUM");
447 assert_eq!(classify_dependency_contribution(2.0), "LOW");
448 assert_eq!(classify_dependency_contribution(0.0), "LOW");
449 }
450}