memscope_rs/export/
export_enhanced.rs

1//! Enhanced export functionality for memory tracking data.
2
3use crate::core::tracker::MemoryTracker;
4use crate::core::types::{AllocationInfo, MemoryStats, TrackingResult, TypeMemoryUsage};
5use crate::utils::{format_bytes, get_category_color, simplify_type_name};
6
7/// Calculate real median and P95 percentiles from allocation sizes
8/// Returns (median_size, p95_size)
9fn calculate_allocation_percentiles(allocations: &[AllocationInfo]) -> (usize, usize) {
10    if allocations.is_empty() {
11        return (0, 0);
12    }
13
14    // Collect all allocation sizes
15    let mut sizes: Vec<usize> = allocations.iter().map(|a| a.size).collect();
16    sizes.sort_unstable();
17
18    let len = sizes.len();
19
20    // Calculate median (50th percentile)
21    let median = if len % 2 == 0 {
22        (sizes[len / 2 - 1] + sizes[len / 2]) / 2
23    } else {
24        sizes[len / 2]
25    };
26
27    // Calculate P95 (95th percentile)
28    let p95_index = ((len as f64) * 0.95) as usize;
29    let p95 = if p95_index >= len {
30        sizes[len - 1]
31    } else {
32        sizes[p95_index]
33    };
34
35    (median, p95)
36}
37use std::collections::HashMap;
38use std::fs::File;
39use std::io::Write;
40use std::path::Path;
41use svg::node::element::{Circle, Rectangle, Text as SvgText};
42use svg::Document;
43
44/// Enhanced type information processing with variable names and inner type extraction
45pub fn enhance_type_information(
46    memory_by_type: &[TypeMemoryUsage],
47    allocations: &[AllocationInfo],
48) -> Vec<EnhancedTypeInfo> {
49    let mut enhanced_types = Vec::new();
50    let mut inner_type_stats: std::collections::HashMap<String, (usize, usize, Vec<String>)> =
51        std::collections::HashMap::new();
52
53    for usage in memory_by_type {
54        // Skip unknown types
55        if usage.type_name == "Unknown" {
56            continue;
57        }
58
59        // Use enhanced type analysis for better categorization
60        let (simplified_name, category, subcategory) =
61            analyze_type_with_detailed_subcategory(&usage.type_name);
62
63        // Collect variable names for this type
64        let variable_names: Vec<String> = allocations
65            .iter()
66            .filter_map(|alloc| {
67                if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
68                    let (alloc_simplified, _, _) =
69                        analyze_type_with_detailed_subcategory(type_name);
70                    if alloc_simplified == simplified_name {
71                        Some(var_name.to_string())
72                    } else {
73                        None
74                    }
75                } else {
76                    None
77                }
78            })
79            .take(5) // Limit to 5 variable names
80            .collect();
81
82        // Add the main type with subcategory information
83        enhanced_types.push(EnhancedTypeInfo {
84            simplified_name,
85            category,
86            subcategory,
87            total_size: usage.total_size,
88            allocation_count: usage.allocation_count,
89            variable_names,
90        });
91
92        // Extract and accumulate inner types (e.g., i32 from Vec<i32>, u8 from Vec<u8>)
93        extract_and_accumulate_inner_types_enhanced(
94            &usage.type_name,
95            usage.total_size,
96            usage.allocation_count,
97            &mut inner_type_stats,
98        );
99    }
100
101    // Add accumulated inner types as separate entries
102    for (inner_type, (total_size, allocation_count, var_names)) in inner_type_stats {
103        let (simplified_name, category, subcategory) =
104            analyze_type_with_detailed_subcategory(&inner_type);
105        // tracing::info!("Adding inner type: '{}' -> '{}' ({}), size: {}, category: {}, subcategory: {}",
106        //               inner_type, simplified_name, allocation_count, total_size, category, subcategory);
107        enhanced_types.push(EnhancedTypeInfo {
108            simplified_name,
109            category,
110            subcategory,
111            total_size,
112            allocation_count,
113            variable_names: var_names.into_iter().take(5).collect(),
114        });
115    }
116
117    // Debug: Print final enhanced types
118    // tracing::info!("Final enhanced types count: {}", enhanced_types.len());
119    // for (i, t) in enhanced_types.iter().enumerate() {
120    //     tracing::info!("Type {}: {} ({} -> {}) - {} bytes", i, t.simplified_name, t.category, t.subcategory, t.total_size);
121    // }
122
123    enhanced_types
124}
125
126/// Enhanced type analysis with detailed subcategory detection
127fn analyze_type_with_detailed_subcategory(type_name: &str) -> (String, String, String) {
128    let clean_type = type_name.trim();
129
130    // Handle empty or explicitly unknown types first
131    if clean_type.is_empty() || clean_type == "Unknown" {
132        return (
133            "Unknown Type".to_string(),
134            "Unknown".to_string(),
135            "Other".to_string(),
136        );
137    }
138
139    // Smart Pointers - Check these FIRST before basic types
140    if clean_type.contains("Box<") {
141        let inner = extract_generic_inner_type(clean_type, "Box");
142        return (inner, "Smart Pointers".to_string(), "Box<T>".to_string());
143    }
144
145    if clean_type.contains("Rc<") {
146        let inner = extract_generic_inner_type(clean_type, "Rc");
147        return (inner, "Smart Pointers".to_string(), "Rc<T>".to_string());
148    }
149
150    if clean_type.contains("Arc<") {
151        let inner = extract_generic_inner_type(clean_type, "Arc");
152        return (inner, "Smart Pointers".to_string(), "Arc<T>".to_string());
153    }
154
155    // Collections analysis with precise subcategorization
156    if clean_type.contains("Vec<") || clean_type.contains("vec::Vec") {
157        let inner = extract_generic_inner_type(clean_type, "Vec");
158        return (
159            format!("Vec<{inner}>"),
160            "Collections".to_string(),
161            "Vec<T>".to_string(),
162        );
163    }
164
165    if clean_type.contains("HashMap") || clean_type.contains("hash_map") {
166        return (
167            "HashMap<K,V>".to_string(),
168            "Collections".to_string(),
169            "HashMap<K,V>".to_string(),
170        );
171    }
172
173    if clean_type.contains("HashSet") || clean_type.contains("hash_set") {
174        return (
175            "HashSet<T>".to_string(),
176            "Collections".to_string(),
177            "HashSet<T>".to_string(),
178        );
179    }
180
181    if clean_type.contains("BTreeMap") || clean_type.contains("btree_map") {
182        return (
183            "BTreeMap<K,V>".to_string(),
184            "Collections".to_string(),
185            "BTreeMap<K,V>".to_string(),
186        );
187    }
188
189    if clean_type.contains("BTreeSet") || clean_type.contains("btree_set") {
190        return (
191            "BTreeSet<T>".to_string(),
192            "Collections".to_string(),
193            "BTreeSet<T>".to_string(),
194        );
195    }
196
197    if clean_type.contains("VecDeque") || clean_type.contains("vec_deque") {
198        return (
199            "VecDeque<T>".to_string(),
200            "Collections".to_string(),
201            "VecDeque<T>".to_string(),
202        );
203    }
204
205    if clean_type.contains("LinkedList") {
206        return (
207            "LinkedList<T>".to_string(),
208            "Collections".to_string(),
209            "LinkedList<T>".to_string(),
210        );
211    }
212
213    // Basic Types analysis with precise subcategorization
214    if clean_type.contains("String") || clean_type.contains("string::String") {
215        return (
216            "String".to_string(),
217            "Basic Types".to_string(),
218            "Strings".to_string(),
219        );
220    }
221
222    if clean_type.contains("&str") || clean_type == "str" {
223        return (
224            "&str".to_string(),
225            "Basic Types".to_string(),
226            "Strings".to_string(),
227        );
228    }
229
230    // Integer types - exact matching
231    if clean_type == "i32" || clean_type.ends_with("::i32") {
232        return (
233            "i32".to_string(),
234            "Basic Types".to_string(),
235            "Integers".to_string(),
236        );
237    }
238    if clean_type == "i64" || clean_type.ends_with("::i64") {
239        return (
240            "i64".to_string(),
241            "Basic Types".to_string(),
242            "Integers".to_string(),
243        );
244    }
245    if clean_type == "u32" || clean_type.ends_with("::u32") {
246        return (
247            "u32".to_string(),
248            "Basic Types".to_string(),
249            "Integers".to_string(),
250        );
251    }
252    if clean_type == "u64" || clean_type.ends_with("::u64") {
253        return (
254            "u64".to_string(),
255            "Basic Types".to_string(),
256            "Integers".to_string(),
257        );
258    }
259    if clean_type == "usize" || clean_type.ends_with("::usize") {
260        return (
261            "usize".to_string(),
262            "Basic Types".to_string(),
263            "Integers".to_string(),
264        );
265    }
266    if clean_type == "isize" || clean_type.ends_with("::isize") {
267        return (
268            "isize".to_string(),
269            "Basic Types".to_string(),
270            "Integers".to_string(),
271        );
272    }
273    if clean_type == "i8" || clean_type.ends_with("::i8") {
274        return (
275            "i8".to_string(),
276            "Basic Types".to_string(),
277            "Integers".to_string(),
278        );
279    }
280    if clean_type == "u8" || clean_type.ends_with("::u8") {
281        return (
282            "u8".to_string(),
283            "Basic Types".to_string(),
284            "Integers".to_string(),
285        );
286    }
287    if clean_type == "i16" || clean_type.ends_with("::i16") {
288        return (
289            "i16".to_string(),
290            "Basic Types".to_string(),
291            "Integers".to_string(),
292        );
293    }
294    if clean_type == "u16" || clean_type.ends_with("::u16") {
295        return (
296            "u16".to_string(),
297            "Basic Types".to_string(),
298            "Integers".to_string(),
299        );
300    }
301
302    // Float types
303    if clean_type == "f32" || clean_type.ends_with("::f32") {
304        return (
305            "f32".to_string(),
306            "Basic Types".to_string(),
307            "Floats".to_string(),
308        );
309    }
310    if clean_type == "f64" || clean_type.ends_with("::f64") {
311        return (
312            "f64".to_string(),
313            "Basic Types".to_string(),
314            "Floats".to_string(),
315        );
316    }
317
318    // Other basic types
319    if clean_type == "bool" || clean_type.ends_with("::bool") {
320        return (
321            "bool".to_string(),
322            "Basic Types".to_string(),
323            "Booleans".to_string(),
324        );
325    }
326    if clean_type == "char" || clean_type.ends_with("::char") {
327        return (
328            "char".to_string(),
329            "Basic Types".to_string(),
330            "Characters".to_string(),
331        );
332    }
333
334    // Fall back to original logic for other types
335    let (simplified_name, category) = simplify_type_name(clean_type);
336    (simplified_name, category, "Other".to_string())
337}
338
339/// Extract generic inner type for display
340fn extract_generic_inner_type(type_name: &str, container: &str) -> String {
341    if let Some(start) = type_name.find(&format!("{container}<")) {
342        let start = start + container.len() + 1;
343        if let Some(end) = type_name[start..].rfind('>') {
344            let inner = &type_name[start..start + end];
345            return inner.split("::").last().unwrap_or(inner).to_string();
346        }
347    }
348    "?".to_string()
349}
350
351/// Extract inner types from complex types and accumulate their statistics (enhanced version)
352fn extract_and_accumulate_inner_types_enhanced(
353    type_name: &str,
354    size: usize,
355    count: usize,
356    stats: &mut std::collections::HashMap<String, (usize, usize, Vec<String>)>,
357) {
358    // Extract inner types from generic containers
359    let inner_types = extract_inner_primitive_types_enhanced(type_name);
360
361    // Debug: Print what we found
362    // if !inner_types.is_empty() {
363    // tracing::info!("Found inner types in '{}': {:?}", type_name, inner_types);
364    // }
365
366    for inner_type in inner_types {
367        let entry = stats.entry(inner_type).or_insert((0, 0, Vec::new()));
368        entry.0 += size / 4; // Rough estimation of inner type contribution
369        entry.1 += count;
370        entry.2.push(format!("from {type_name}"));
371        // tracing::info!("Accumulated {} bytes for inner type '{}' from '{}'", size / 4, inner_type, type_name);
372    }
373}
374
375/// Extract primitive types from complex type signatures (enhanced version)
376fn extract_inner_primitive_types_enhanced(type_name: &str) -> Vec<String> {
377    let mut inner_types = Vec::new();
378
379    // Common primitive type patterns
380    let primitives = [
381        "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
382        "f32", "f64", "bool", "char",
383    ];
384
385    for primitive in &primitives {
386        if type_name.contains(primitive) {
387            // Make sure it's actually the primitive type, not part of another word
388            if type_name.contains(&format!("{primitive}>"))
389                || type_name.contains(&format!("{primitive},"))
390                || type_name.contains(&format!(" {primitive}"))
391                || type_name.ends_with(primitive)
392            {
393                inner_types.push(primitive.to_string());
394            }
395        }
396    }
397
398    inner_types
399}
400
401/// Categorize allocations for better visualization
402pub fn categorize_allocations(allocations: &[AllocationInfo]) -> Vec<AllocationCategory> {
403    let mut categories: HashMap<String, AllocationCategory> = HashMap::new();
404
405    for allocation in allocations {
406        // Skip allocations without variable names or with unknown types
407        if allocation.var_name.is_none()
408            || allocation.type_name.as_ref().is_none_or(|t| t == "Unknown")
409        {
410            continue;
411        }
412
413        let type_name = allocation
414            .type_name
415            .as_ref()
416            .expect("Type name should be available after None check");
417        let (_, category_name) = simplify_type_name(type_name);
418        let category_name_clone = category_name.clone();
419
420        let category =
421            categories
422                .entry(category_name.clone())
423                .or_insert_with(|| AllocationCategory {
424                    name: category_name_clone,
425                    allocations: Vec::new(),
426                    total_size: 0,
427                    color: get_category_color(&category_name),
428                });
429
430        category.allocations.push(allocation.clone());
431        category.total_size += allocation.size;
432    }
433
434    let mut result: Vec<_> = categories.into_values().collect();
435    result.sort_by(|a, b| b.total_size.cmp(&a.total_size));
436    result
437}
438
439/// Categorize enhanced type information for consistent visualization
440/// This ensures "Tracked Variables by Category" uses the same data as "Memory Usage by Type"
441pub fn categorize_enhanced_allocations(
442    enhanced_types: &[EnhancedTypeInfo],
443) -> Vec<AllocationCategory> {
444    let mut categories: HashMap<String, AllocationCategory> = HashMap::new();
445
446    for enhanced_type in enhanced_types {
447        // Skip unknown types
448        if enhanced_type.simplified_name == "Unknown" {
449            continue;
450        }
451
452        let category_name = &enhanced_type.category;
453
454        let category = categories
455            .entry(category_name.to_string())
456            .or_insert_with(|| AllocationCategory {
457                name: category_name.to_string(),
458                allocations: Vec::new(),
459                total_size: 0,
460                color: get_category_color(category_name),
461            });
462
463        // Create synthetic allocation info for display
464        let mut synthetic_allocation = AllocationInfo::new(0, enhanced_type.total_size);
465        synthetic_allocation.var_name = Some(enhanced_type.variable_names.join(", "));
466        synthetic_allocation.type_name = Some(enhanced_type.simplified_name.clone());
467
468        category.allocations.push(synthetic_allocation);
469        category.total_size += enhanced_type.total_size;
470    }
471
472    let mut result: Vec<_> = categories.into_values().collect();
473    result.sort_by(|a, b| b.total_size.cmp(&a.total_size));
474    result
475}
476
477/// Enhanced type information with variable names and categorization
478#[derive(Debug, Clone)]
479pub struct EnhancedTypeInfo {
480    /// Simplified type name for display
481    pub simplified_name: String,
482    /// Category this type belongs to
483    pub category: String,
484    /// Subcategory for fine-grained classification
485    pub subcategory: String,
486    /// Total memory size used by this type
487    pub total_size: usize,
488    /// Number of allocations of this type
489    pub allocation_count: usize,
490    /// Variable names associated with this type
491    pub variable_names: Vec<String>,
492}
493
494/// Allocation category for grouping related allocations
495#[derive(Debug, Clone)]
496pub struct AllocationCategory {
497    /// Category name
498    pub name: String,
499    /// Allocations in this category
500    pub allocations: Vec<AllocationInfo>,
501    /// Total size of all allocations in this category
502    pub total_size: usize,
503    /// Color used for visualization
504    pub color: String,
505}
506
507/// Enhanced SVG export with comprehensive visualization
508pub fn export_enhanced_svg<P: AsRef<Path>>(tracker: &MemoryTracker, path: P) -> TrackingResult<()> {
509    let path = path.as_ref();
510
511    // Create parent directories if needed
512    if let Some(parent) = path.parent() {
513        if !parent.exists() {
514            std::fs::create_dir_all(parent)?;
515        }
516    }
517
518    let active_allocations = tracker.get_active_allocations()?;
519    let memory_by_type = tracker.get_memory_by_type()?;
520    let stats = tracker.get_stats()?;
521
522    // Filter out unknown types and enhance type information
523    let enhanced_memory_by_type = enhance_type_information(&memory_by_type, &active_allocations);
524    let _categorized_allocations = categorize_allocations(&active_allocations);
525
526    // Create COMPACT SVG document - REDUCED HEIGHT for space efficiency
527    let mut document = Document::new()
528        .set("viewBox", (0, 0, 1800, 400))
529        .set("width", 1800)
530        .set("height", 400)
531        .set(
532            "style",
533            "background: linear-gradient(135deg, #2C3E50 0%, #34495E 100%); font-family: 'Segoe UI', Arial, sans-serif;",
534        );
535
536    // COMPACT LAYOUT - Only essential components for 400px height
537
538    // Title (y: 0-50)
539    let title = SvgText::new("Top 3 Memory Analysis - Compact View")
540        .set("x", 700)
541        .set("y", 30)
542        .set("text-anchor", "middle")
543        .set("font-size", 24)
544        .set("font-weight", "bold")
545        .set("fill", "#FFFFFF");
546    document = document.add(title);
547
548    // Only show TOP 3 type chart (y: 60-350)
549    if !enhanced_memory_by_type.is_empty() {
550        document = add_compact_type_chart(document, &enhanced_memory_by_type)?;
551    }
552
553    // Compact summary (y: 350-400)
554    document = add_compact_summary(document, &stats, &active_allocations)?;
555
556    // Write SVG to file
557    let mut file = File::create(path)?;
558    write!(file, "{document}")?;
559
560    Ok(())
561}
562
563/// Add compact type chart - TOP 3 ONLY with progress bars
564fn add_compact_type_chart(
565    mut document: Document,
566    types: &[EnhancedTypeInfo],
567) -> TrackingResult<Document> {
568    let chart_x = 100;
569    let chart_y = 60;
570    let chart_width = 1200;
571
572    if types.is_empty() {
573        let no_data = SvgText::new("No tracked variables found")
574            .set("x", chart_x + chart_width / 2)
575            .set("y", 200)
576            .set("text-anchor", "middle")
577            .set("font-size", 16)
578            .set("fill", "#E74C3C");
579        document = document.add(no_data);
580        return Ok(document);
581    }
582
583    let max_size = types.iter().map(|t| t.total_size).max().unwrap_or(1);
584
585    // Show TOP 3 ONLY
586    for (i, type_info) in types.iter().take(3).enumerate() {
587        let y = chart_y + (i as i32) * 80;
588
589        // Progress bar background
590        let bg_bar = Rectangle::new()
591            .set("x", chart_x)
592            .set("y", y)
593            .set("width", 600)
594            .set("height", 30)
595            .set("fill", "#34495E")
596            .set("stroke", "#ECF0F1")
597            .set("stroke-width", 1)
598            .set("rx", 6);
599        document = document.add(bg_bar);
600
601        // Progress bar fill
602        let bar_width = ((type_info.total_size as f64 / max_size as f64) * 600.0) as i32;
603        let color = get_category_color(&type_info.category);
604        let progress_bar = Rectangle::new()
605            .set("x", chart_x)
606            .set("y", y)
607            .set("width", bar_width)
608            .set("height", 30)
609            .set("fill", color)
610            .set("rx", 6);
611        document = document.add(progress_bar);
612
613        // Type and size info
614        let content_text = format!(
615            "{} ({} vars) | Total: {}",
616            type_info.simplified_name,
617            type_info.allocation_count,
618            format_bytes(type_info.total_size)
619        );
620
621        let content_label = SvgText::new(content_text)
622            .set("x", chart_x + 10)
623            .set("y", y + 20)
624            .set("font-size", 12)
625            .set("font-weight", "600")
626            .set("fill", "#FFFFFF")
627            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
628        document = document.add(content_label);
629
630        // Progress percentage
631        let percentage = (type_info.total_size as f64 / max_size as f64 * 100.0) as i32;
632        let percent_label = SvgText::new(format!("{percentage}%"))
633            .set("x", chart_x + 720)
634            .set("y", y + 20)
635            .set("font-size", 14)
636            .set("font-weight", "bold")
637            .set("fill", "#ECF0F1");
638        document = document.add(percent_label);
639
640        // Variable names below
641        let var_names_text = if type_info.variable_names.is_empty() {
642            "no tracked vars".to_string()
643        } else {
644            format!("Variables: {}", type_info.variable_names.join(", "))
645        };
646
647        let vars_label = SvgText::new(var_names_text)
648            .set("x", chart_x + 10)
649            .set("y", y + 45)
650            .set("font-size", 9)
651            .set("fill", "#94A3B8")
652            .set("font-style", "italic");
653        document = document.add(vars_label);
654    }
655
656    Ok(document)
657}
658
659/// Add compact summary
660fn add_compact_summary(
661    mut document: Document,
662    stats: &MemoryStats,
663    allocations: &[AllocationInfo],
664) -> TrackingResult<Document> {
665    let tracked_vars = allocations.iter().filter(|a| a.var_name.is_some()).count();
666
667    let summary_text = format!(
668        "Showing TOP 3 memory-consuming types | Total tracked: {} variables | Active memory: {}",
669        tracked_vars,
670        format_bytes(stats.active_memory)
671    );
672
673    let summary = SvgText::new(summary_text)
674        .set("x", 700)
675        .set("y", 370)
676        .set("text-anchor", "middle")
677        .set("font-size", 14)
678        .set("font-weight", "bold")
679        .set("fill", "#ECF0F1");
680    document = document.add(summary);
681
682    Ok(document)
683}
684
685/// Add enhanced header with 8 core metrics - Modern Dashboard Style
686pub fn add_enhanced_header(
687    mut document: Document,
688    stats: &MemoryStats,
689    allocations: &[AllocationInfo],
690) -> TrackingResult<Document> {
691    // Main title with modern styling
692    let title = SvgText::new("Rust Memory Usage Analysis")
693        .set("x", 900)
694        .set("y", 40)
695        .set("text-anchor", "middle")
696        .set("font-size", 28)
697        .set("font-weight", "300")
698        .set("fill", "#2c3e50")
699        .set("style", "letter-spacing: 1px;");
700
701    document = document.add(title);
702
703    // Calculate 8 core metrics with percentage values for progress rings
704    let active_memory = stats.active_memory;
705    let peak_memory = stats.peak_memory;
706    let active_allocations = stats.active_allocations;
707
708    let memory_reclamation_rate = if stats.total_allocated > 0 {
709        (stats.total_deallocated as f64 / stats.total_allocated as f64) * 100.0
710    } else {
711        0.0
712    };
713
714    let allocator_efficiency = if stats.peak_memory > 0 {
715        (stats.active_memory as f64 / stats.peak_memory as f64) * 100.0
716    } else {
717        0.0
718    };
719
720    let (median_alloc_size, p95_alloc_size) = calculate_allocation_percentiles(allocations);
721
722    let memory_fragmentation = if stats.peak_memory > 0 {
723        ((stats.peak_memory - stats.active_memory) as f64 / stats.peak_memory as f64) * 100.0
724    } else {
725        0.0
726    };
727
728    // Define metrics with their display values and progress percentages
729    let metrics = [
730        (
731            "Active Memory",
732            format_bytes(active_memory),
733            (active_memory as f64 / peak_memory.max(1) as f64 * 100.0).min(100.0),
734            "#3498db",
735        ),
736        (
737            "Peak Memory",
738            {
739                let formatted = format_bytes(peak_memory);
740                tracing::info!(
741                    "SVG add_enhanced_header - Formatting peak_memory: {} bytes -> {}",
742                    peak_memory,
743                    formatted
744                );
745                formatted
746            },
747            100.0,
748            "#e74c3c",
749        ),
750        (
751            "Active Allocs",
752            format!("{active_allocations}"),
753            (active_allocations as f64 / 1000.0 * 100.0).min(100.0),
754            "#2ecc71",
755        ),
756        (
757            "Reclamation",
758            format!("{memory_reclamation_rate:.1}%"),
759            memory_reclamation_rate,
760            "#f39c12",
761        ),
762        (
763            "Efficiency",
764            format!("{allocator_efficiency:.1}%"),
765            allocator_efficiency,
766            "#9b59b6",
767        ),
768        (
769            "Median Size",
770            format_bytes(median_alloc_size),
771            (median_alloc_size as f64 / 1024.0 * 100.0).min(100.0),
772            "#1abc9c",
773        ),
774        (
775            "P95 Size",
776            format_bytes(p95_alloc_size),
777            (p95_alloc_size as f64 / 4096.0 * 100.0).min(100.0),
778            "#e67e22",
779        ),
780        (
781            "Fragmentation",
782            format!("{memory_fragmentation:.1}%"),
783            memory_fragmentation,
784            "#95a5a6",
785        ),
786    ];
787
788    // Single row layout parameters
789    let card_width = 200;
790    let card_height = 120;
791    let start_x = 50;
792    let start_y = 130;
793    let spacing_x = 220;
794
795    // Add single section header for all metrics
796    let header = SvgText::new("KEY PERFORMANCE METRICS")
797        .set("x", 900)
798        .set("y", start_y - 20)
799        .set("text-anchor", "middle")
800        .set("font-size", 11)
801        .set("font-weight", "600")
802        .set("fill", "#7f8c8d")
803        .set("style", "letter-spacing: 2px;");
804    document = document.add(header);
805
806    // Render all 8 metrics in a single row
807    for (i, (title, value, percentage, color)) in metrics.iter().enumerate() {
808        let x = start_x + i * spacing_x;
809        let y = start_y;
810
811        // Modern card background with gradient and shadow
812        let card_bg = Rectangle::new()
813            .set("x", x)
814            .set("y", y)
815            .set("width", card_width)
816            .set("height", card_height)
817            .set("fill", "#ffffff")
818            .set("stroke", "none")
819            .set("rx", 12)
820            .set("style", "filter: drop-shadow(0 4px 12px rgba(0,0,0,0.15));");
821
822        // Use simple solid color instead of gradient to avoid SVG compatibility issues
823        if i == 0 {
824            // Skip gradient definition for now - use solid colors
825        }
826
827        document = document.add(card_bg);
828
829        // Progress ring background
830        let ring_center_x = x + 40;
831        let ring_center_y = y + 60;
832        let ring_radius = 25;
833
834        let ring_bg = Circle::new()
835            .set("cx", ring_center_x)
836            .set("cy", ring_center_y)
837            .set("r", ring_radius)
838            .set("fill", "none")
839            .set("stroke", "#ecf0f1")
840            .set("stroke-width", 6);
841        document = document.add(ring_bg);
842
843        // Progress ring foreground
844        let circumference = 2.0 * std::f64::consts::PI * ring_radius as f64;
845        let progress_offset = circumference * (1.0 - percentage / 100.0);
846
847        let progress_ring = Circle::new()
848            .set("cx", ring_center_x)
849            .set("cy", ring_center_y)
850            .set("r", ring_radius)
851            .set("fill", "none")
852            .set("stroke", *color)
853            .set("stroke-width", 6)
854            .set("stroke-linecap", "round")
855            .set(
856                "stroke-dasharray",
857                format!("{circumference} {circumference}"),
858            )
859            .set("stroke-dashoffset", progress_offset)
860            .set(
861                "transform",
862                format!("rotate(-90 {ring_center_x} {ring_center_y})"),
863            )
864            .set("style", "transition: stroke-dashoffset 0.5s ease;");
865        document = document.add(progress_ring);
866
867        // Percentage text in center of ring
868        let percent_text = SvgText::new(format!("{percentage:.0}%"))
869            .set("x", ring_center_x)
870            .set("y", ring_center_y + 4)
871            .set("text-anchor", "middle")
872            .set("font-size", 12)
873            .set("font-weight", "bold")
874            .set("fill", *color);
875        document = document.add(percent_text);
876
877        // Metric title
878        let title_text = SvgText::new(*title)
879            .set("x", x + 90)
880            .set("y", y + 35)
881            .set("font-size", 12)
882            .set("font-weight", "600")
883            .set("fill", "#2c3e50");
884        document = document.add(title_text);
885
886        // Metric value with larger, prominent display
887        let value_text = SvgText::new(value)
888            .set("x", x + 90)
889            .set("y", y + 55)
890            .set("font-size", 16)
891            .set("font-weight", "bold")
892            .set("fill", "#2c3e50");
893        document = document.add(value_text);
894
895        // Status indicator based on percentage
896        let status_color = if *percentage >= 80.0 {
897            "#e74c3c" // Red for high values
898        } else if *percentage >= 50.0 {
899            "#f39c12" // Orange for medium values
900        } else {
901            "#27ae60" // Green for low values
902        };
903
904        let status_dot = Circle::new()
905            .set("cx", x + 90)
906            .set("cy", y + 75)
907            .set("r", 4)
908            .set("fill", status_color);
909        document = document.add(status_dot);
910
911        // Status text
912        let status_text = if *percentage >= 80.0 {
913            "HIGH"
914        } else if *percentage >= 50.0 {
915            "MEDIUM"
916        } else {
917            "OPTIMAL"
918        };
919
920        let status_label = SvgText::new(status_text)
921            .set("x", x + 105)
922            .set("y", y + 79)
923            .set("font-size", 9)
924            .set("font-weight", "600")
925            .set("fill", status_color);
926        document = document.add(status_label);
927    }
928
929    Ok(document)
930}
931
932/// Add enhanced treemap chart with real data
933pub fn add_enhanced_type_chart(
934    mut document: Document,
935    types: &[EnhancedTypeInfo],
936) -> TrackingResult<Document> {
937    // Optimized position to avoid overlap with other modules
938    let chart_x = 50;
939    let chart_y = 320; // Moved up significantly after removing timeline dashboard
940    let chart_width = 850;
941    let chart_height = 300; // Reduced height to prevent overlap
942
943    // Chart title
944    let title = SvgText::new("Memory Usage by Type - Treemap Visualization")
945        .set("x", chart_x + chart_width / 2)
946        .set("y", chart_y - 10)
947        .set("text-anchor", "middle")
948        .set("font-size", 16)
949        .set("font-weight", "bold")
950        .set("fill", "#2c3e50");
951
952    document = document.add(title);
953
954    // Add treemap styles
955    let styles = svg::node::element::Style::new(
956        r#"
957        .integrated-treemap-rect { 
958            transition: all 0.3s ease; 
959            cursor: pointer; 
960            stroke: #ffffff; 
961            stroke-width: 2; 
962        }
963        .integrated-treemap-rect:hover { 
964            stroke: #2c3e50; 
965            stroke-width: 3; 
966            filter: brightness(1.1); 
967        }
968        .integrated-treemap-label { 
969            fill: #ffffff; 
970            font-weight: 700; 
971            text-anchor: middle; 
972            dominant-baseline: middle; 
973            pointer-events: none;
974            text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
975        }
976        .integrated-treemap-percentage { 
977            fill: #f8f9fa; 
978            font-weight: 600;
979            text-anchor: middle; 
980            dominant-baseline: middle; 
981            pointer-events: none;
982            text-shadow: 1px 1px 2px rgba(0,0,0,0.6);
983        }
984    "#,
985    );
986    document = document.add(styles);
987
988    // Create integrated treemap layout
989    let treemap_area = IntegratedTreemapArea {
990        x: chart_x as f64,
991        y: chart_y as f64,
992        width: chart_width as f64,
993        height: chart_height as f64,
994    };
995
996    // Build and render real data treemap
997    document = render_real_data_treemap(document, treemap_area, types)?;
998
999    // Note: Legend is now integrated within the treemap rendering, no separate legend needed
1000
1001    Ok(document)
1002}
1003
1004/// Integrated treemap area structure
1005#[derive(Debug, Clone)]
1006struct IntegratedTreemapArea {
1007    x: f64,
1008    y: f64,
1009    width: f64,
1010    height: f64,
1011}
1012
1013/// Render integrated treemap with real data - placeholder function
1014/// Render treemap with real memory data matching task.md layout
1015fn render_real_data_treemap(
1016    mut document: Document,
1017    area: IntegratedTreemapArea,
1018    types: &[EnhancedTypeInfo],
1019) -> TrackingResult<Document> {
1020    if types.is_empty() {
1021        let no_data_rect = Rectangle::new()
1022            .set("x", area.x)
1023            .set("y", area.y)
1024            .set("width", area.width)
1025            .set("height", area.height)
1026            .set("fill", "#f8f9fa")
1027            .set("stroke", "#dee2e6")
1028            .set("stroke-width", 2)
1029            .set("rx", 10);
1030        document = document.add(no_data_rect);
1031
1032        let no_data_text = SvgText::new("No Memory Type Data Available")
1033            .set("x", area.x + area.width / 2.0)
1034            .set("y", area.y + area.height / 2.0)
1035            .set("text-anchor", "middle")
1036            .set("font-size", 16)
1037            .set("font-weight", "bold")
1038            .set("fill", "#6c757d");
1039        document = document.add(no_data_text);
1040
1041        return Ok(document);
1042    }
1043
1044    // Calculate total memory for percentage calculations
1045    let total_memory: usize = types.iter().map(|t| t.total_size).sum();
1046    if total_memory == 0 {
1047        return Ok(document);
1048    }
1049
1050    // Group types by category to match task.md structure
1051    let mut collections_types = Vec::new();
1052    let mut basic_types = Vec::new();
1053    let mut smart_pointers_types = Vec::new();
1054    let mut other_types = Vec::new();
1055
1056    for type_info in types {
1057        match type_info.category.as_str() {
1058            "Collections" => collections_types.push(type_info),
1059            "Basic Types" => basic_types.push(type_info),
1060            "Strings" => basic_types.push(type_info), // Legacy support - redirect to Basic Types
1061            "Smart Pointers" => smart_pointers_types.push(type_info),
1062            _ => other_types.push(type_info),
1063        }
1064    }
1065
1066    // Calculate category totals
1067    let _collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1068    let _basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1069    let _smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1070    let _other_total: usize = other_types.iter().map(|t| t.total_size).sum();
1071
1072    // Analyze data distribution for intelligent layout decision
1073    let layout_strategy = analyze_data_distribution(
1074        &collections_types,
1075        &basic_types,
1076        &smart_pointers_types,
1077        &other_types,
1078        total_memory,
1079    );
1080
1081    // Build treemap structure based on intelligent analysis
1082    let treemap_data = build_adaptive_treemap_data(
1083        collections_types,
1084        basic_types,
1085        smart_pointers_types,
1086        other_types,
1087        total_memory,
1088        &layout_strategy,
1089    );
1090
1091    // Render treemap using adaptive algorithm
1092    document = render_adaptive_treemap(document, area, &treemap_data, &layout_strategy)?;
1093
1094    Ok(document)
1095}
1096
1097/// Treemap data structure for hierarchical visualization
1098#[derive(Debug, Clone)]
1099struct TreemapNode {
1100    /// Node name
1101    name: String,
1102    /// Node size
1103    size: usize,
1104    /// Node percentage
1105    percentage: f64,
1106    /// Node color
1107    color: String,
1108    /// Node children
1109    children: Vec<TreemapNode>,
1110}
1111
1112/// Layout strategy for treemap rendering
1113#[derive(Debug, Clone)]
1114#[allow(clippy::enum_variant_names)]
1115enum TreemapLayoutStrategy {
1116    /// Full treemap with all categories and subcategories
1117    FullLayout,
1118    /// Simplified layout focusing on dominant category
1119    DominantCategoryLayout { dominant_category: String },
1120    /// Minimal layout for simple programs
1121    MinimalLayout,
1122    /// Collections-only layout
1123    CollectionsOnlyLayout,
1124    /// Basic types only layout
1125    BasicTypesOnlyLayout,
1126}
1127
1128/// Analyze data distribution to determine optimal layout strategy
1129fn analyze_data_distribution(
1130    collections_types: &[&EnhancedTypeInfo],
1131    basic_types: &[&EnhancedTypeInfo],
1132    smart_pointers_types: &[&EnhancedTypeInfo],
1133    _other_types: &[&EnhancedTypeInfo],
1134    total_memory: usize,
1135) -> TreemapLayoutStrategy {
1136    let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1137    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1138    let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1139
1140    let collections_percentage = if total_memory > 0 {
1141        (collections_total as f64 / total_memory as f64) * 100.0
1142    } else {
1143        0.0
1144    };
1145    let basic_types_percentage = if total_memory > 0 {
1146        (basic_types_total as f64 / total_memory as f64) * 100.0
1147    } else {
1148        0.0
1149    };
1150    let smart_pointers_percentage = if total_memory > 0 {
1151        (smart_pointers_total as f64 / total_memory as f64) * 100.0
1152    } else {
1153        0.0
1154    };
1155
1156    // Count non-zero categories
1157    let active_categories = [
1158        (collections_total > 0, "Collections"),
1159        (basic_types_total > 0, "Basic Types"),
1160        (smart_pointers_total > 0, "Smart Pointers"),
1161    ]
1162    .iter()
1163    .filter(|(active, _)| *active)
1164    .count();
1165
1166    match active_categories {
1167        0 => TreemapLayoutStrategy::MinimalLayout,
1168        1 => {
1169            // Single dominant category
1170            if collections_percentage > 80.0 {
1171                TreemapLayoutStrategy::CollectionsOnlyLayout
1172            } else if basic_types_percentage > 80.0 {
1173                TreemapLayoutStrategy::BasicTypesOnlyLayout
1174            } else {
1175                TreemapLayoutStrategy::DominantCategoryLayout {
1176                    dominant_category: if collections_total > basic_types_total
1177                        && collections_total > smart_pointers_total
1178                    {
1179                        "Collections".to_string()
1180                    } else if basic_types_total > smart_pointers_total {
1181                        "Basic Types".to_string()
1182                    } else {
1183                        "Smart Pointers".to_string()
1184                    },
1185                }
1186            }
1187        }
1188        2 => {
1189            // Two categories - use simplified layout
1190            if collections_percentage > 70.0
1191                || basic_types_percentage > 70.0
1192                || smart_pointers_percentage > 70.0
1193            {
1194                TreemapLayoutStrategy::DominantCategoryLayout {
1195                    dominant_category: if collections_total > basic_types_total
1196                        && collections_total > smart_pointers_total
1197                    {
1198                        "Collections".to_string()
1199                    } else if basic_types_total > smart_pointers_total {
1200                        "Basic Types".to_string()
1201                    } else {
1202                        "Smart Pointers".to_string()
1203                    },
1204                }
1205            } else {
1206                TreemapLayoutStrategy::FullLayout
1207            }
1208        }
1209        _ => TreemapLayoutStrategy::FullLayout, // 3+ categories - use full layout
1210    }
1211}
1212
1213/// Build adaptive treemap data structure based on layout strategy
1214fn build_adaptive_treemap_data(
1215    collections_types: Vec<&EnhancedTypeInfo>,
1216    basic_types: Vec<&EnhancedTypeInfo>,
1217    smart_pointers_types: Vec<&EnhancedTypeInfo>,
1218    _other_types: Vec<&EnhancedTypeInfo>,
1219    total_memory: usize,
1220    strategy: &TreemapLayoutStrategy,
1221) -> Vec<TreemapNode> {
1222    let mut treemap_nodes = Vec::new();
1223
1224    match strategy {
1225        TreemapLayoutStrategy::MinimalLayout => {
1226            // Show a simple "No significant data" message
1227            treemap_nodes.push(TreemapNode {
1228                name: "No Significant Memory Usage".to_string(),
1229                size: total_memory.max(1),
1230                percentage: 100.0,
1231                color: "#95a5a6".to_string(),
1232                children: Vec::new(),
1233            });
1234        }
1235        TreemapLayoutStrategy::CollectionsOnlyLayout => {
1236            // Show only Collections with detailed subcategories
1237            let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1238            if collections_total > 0 {
1239                treemap_nodes.push(build_collections_node(&collections_types, total_memory));
1240            }
1241        }
1242        TreemapLayoutStrategy::BasicTypesOnlyLayout => {
1243            // Show only Basic Types with detailed subcategories
1244            let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1245            if basic_types_total > 0 {
1246                treemap_nodes.push(build_basic_types_node(&basic_types, total_memory));
1247            }
1248        }
1249        TreemapLayoutStrategy::DominantCategoryLayout { dominant_category } => {
1250            // Show dominant category with details, others simplified
1251            match dominant_category.as_str() {
1252                "Collections" => {
1253                    let collections_total: usize =
1254                        collections_types.iter().map(|t| t.total_size).sum();
1255                    if collections_total > 0 {
1256                        treemap_nodes
1257                            .push(build_collections_node(&collections_types, total_memory));
1258                    }
1259                    // Add other categories as simple nodes
1260                    add_simple_categories(
1261                        &mut treemap_nodes,
1262                        &basic_types,
1263                        &smart_pointers_types,
1264                        total_memory,
1265                    );
1266                }
1267                "Basic Types" => {
1268                    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1269                    if basic_types_total > 0 {
1270                        treemap_nodes.push(build_basic_types_node(&basic_types, total_memory));
1271                    }
1272                    // Add other categories as simple nodes
1273                    add_simple_categories_alt(
1274                        &mut treemap_nodes,
1275                        &collections_types,
1276                        &smart_pointers_types,
1277                        total_memory,
1278                    );
1279                }
1280                _ => {
1281                    // Smart Pointers dominant - use full layout
1282                    build_full_layout(
1283                        &mut treemap_nodes,
1284                        &collections_types,
1285                        &basic_types,
1286                        &smart_pointers_types,
1287                        total_memory,
1288                    );
1289                }
1290            }
1291        }
1292        TreemapLayoutStrategy::FullLayout => {
1293            // Show all categories with full details
1294            build_full_layout(
1295                &mut treemap_nodes,
1296                &collections_types,
1297                &basic_types,
1298                &smart_pointers_types,
1299                total_memory,
1300            );
1301        }
1302    }
1303
1304    treemap_nodes
1305}
1306
1307/// Build Collections node with comprehensive subcategories using enhanced type info
1308fn build_collections_node(
1309    collections_types: &[&EnhancedTypeInfo],
1310    total_memory: usize,
1311) -> TreemapNode {
1312    let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1313
1314    // Debug: Print what collections we received
1315    // tracing::info!("build_collections_node received {} types, total: {} bytes", collections_types.len(), collections_total);
1316    // 2
1317
1318    if collections_total == 0 {
1319        return TreemapNode {
1320            name: "Collections".to_string(),
1321            size: 1,
1322            percentage: 0.0,
1323            color: "#ecf0f1".to_string(),
1324            children: Vec::new(),
1325        };
1326    }
1327
1328    // Group by subcategory for more accurate classification
1329    let mut subcategory_groups: std::collections::HashMap<String, Vec<&EnhancedTypeInfo>> =
1330        std::collections::HashMap::new();
1331
1332    for collection_type in collections_types {
1333        subcategory_groups
1334            .entry(collection_type.subcategory.clone())
1335            .or_default()
1336            .push(collection_type);
1337    }
1338
1339    let mut collections_children = Vec::new();
1340
1341    // Define colors for each subcategory
1342    let subcategory_colors = [
1343        ("Vec<T>", "#e74c3c"),
1344        ("HashMap<K,V>", "#3498db"),
1345        ("HashSet<T>", "#9b59b6"),
1346        ("BTreeMap<K,V>", "#2ecc71"),
1347        ("BTreeSet<T>", "#27ae60"),
1348        ("VecDeque<T>", "#f39c12"),
1349        ("LinkedList<T>", "#e67e22"),
1350        ("Other", "#95a5a6"),
1351    ]
1352    .iter()
1353    .cloned()
1354    .collect::<std::collections::HashMap<&str, &str>>();
1355
1356    for (subcategory, types) in subcategory_groups {
1357        let category_total: usize = types.iter().map(|t| t.total_size).sum();
1358
1359        // Debug: Print subcategory details
1360        // tracing::info!("Collections Subcategory '{}': {} types, total: {} bytes",
1361        //               subcategory, types.len(), category_total);
1362        // for t in &types {
1363        //     tracing::info!("  - '{}' - {} bytes", t.simplified_name, t.total_size);
1364        // }
1365
1366        if category_total > 0 {
1367            let relative_percentage = (category_total as f64 / collections_total as f64) * 100.0;
1368            let color = subcategory_colors
1369                .get(subcategory.as_str())
1370                .unwrap_or(&"#95a5a6")
1371                .to_string();
1372
1373            collections_children.push(TreemapNode {
1374                name: format!("{subcategory} ({relative_percentage:.1}%)"),
1375                size: category_total,
1376                percentage: (category_total as f64 / total_memory as f64) * 100.0,
1377                color,
1378                children: Vec::new(),
1379            });
1380        }
1381    }
1382
1383    // Sort children by size (largest first) for better visual hierarchy
1384    collections_children.sort_by(|a, b| b.size.cmp(&a.size));
1385
1386    TreemapNode {
1387        name: format!(
1388            "Collections ({:.1}%)",
1389            (collections_total as f64 / total_memory as f64) * 100.0
1390        ),
1391        size: collections_total,
1392        percentage: (collections_total as f64 / total_memory as f64) * 100.0,
1393        color: "#ecf0f1".to_string(),
1394        children: collections_children,
1395    }
1396}
1397
1398/// Build Basic Types node with comprehensive subcategories using enhanced type info
1399fn build_basic_types_node(basic_types: &[&EnhancedTypeInfo], total_memory: usize) -> TreemapNode {
1400    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1401
1402    // Debug: Print what basic types we received
1403    // tracing::info!("build_basic_types_node received {} types, total: {} bytes", basic_types.len(), basic_types_total);
1404    // for (i, bt) in basic_types.iter().enumerate() {
1405    //     tracing::info!("  Basic type {}: '{}' (subcategory: '{}') - {} bytes", i, bt.simplified_name, bt.subcategory, bt.total_size);
1406    // }
1407
1408    if basic_types_total == 0 {
1409        return TreemapNode {
1410            name: "Basic Types".to_string(),
1411            size: 1,
1412            percentage: 0.0,
1413            color: "#ecf0f1".to_string(),
1414            children: Vec::new(),
1415        };
1416    }
1417
1418    // Group by subcategory for more accurate classification
1419    let mut subcategory_groups: std::collections::HashMap<String, Vec<&EnhancedTypeInfo>> =
1420        std::collections::HashMap::new();
1421
1422    for basic_type in basic_types {
1423        subcategory_groups
1424            .entry(basic_type.subcategory.clone())
1425            .or_default()
1426            .push(basic_type);
1427    }
1428
1429    let mut basic_types_children = Vec::new();
1430
1431    // Define colors for each subcategory
1432    let subcategory_colors = [
1433        ("Strings", "#2ecc71"),
1434        ("Integers", "#3498db"),
1435        ("Floats", "#e74c3c"),
1436        ("Booleans", "#f39c12"),
1437        ("Characters", "#9b59b6"),
1438        ("Arrays", "#1abc9c"),
1439        ("References", "#e67e22"),
1440        ("Other", "#95a5a6"),
1441    ]
1442    .iter()
1443    .cloned()
1444    .collect::<std::collections::HashMap<&str, &str>>();
1445
1446    for (subcategory, types) in subcategory_groups {
1447        let category_total: usize = types.iter().map(|t| t.total_size).sum();
1448
1449        // Debug: Print subcategory details
1450        // tracing::info!("Basic Types Subcategory '{}': {} types, total: {} bytes",
1451        //               subcategory, types.len(), category_total);
1452        // for t in &types {
1453        //     tracing::info!("  - '{}' - {} bytes", t.simplified_name, t.total_size);
1454        // }
1455
1456        if category_total > 0 {
1457            let relative_percentage = (category_total as f64 / basic_types_total as f64) * 100.0;
1458            let color = subcategory_colors
1459                .get(subcategory.as_str())
1460                .unwrap_or(&"#95a5a6")
1461                .to_string();
1462
1463            basic_types_children.push(TreemapNode {
1464                name: format!("{subcategory} ({relative_percentage:.1}%)"),
1465                size: category_total,
1466                percentage: (category_total as f64 / total_memory as f64) * 100.0,
1467                color,
1468                children: Vec::new(),
1469            });
1470        }
1471    }
1472
1473    // Sort children by size (largest first) for better visual hierarchy
1474    basic_types_children.sort_by(|a, b| b.size.cmp(&a.size));
1475
1476    // Debug: Print final children
1477    // tracing::info!("Basic Types node will have {} children:", basic_types_children.len());
1478    // for (i, child) in basic_types_children.iter().enumerate() {
1479    //     tracing::info!("  Child {}: '{}' - {} bytes", i, child.name, child.size);
1480    // }
1481
1482    TreemapNode {
1483        name: format!(
1484            "Basic Types ({:.1}%)",
1485            (basic_types_total as f64 / total_memory as f64) * 100.0
1486        ),
1487        size: basic_types_total,
1488        percentage: (basic_types_total as f64 / total_memory as f64) * 100.0,
1489        color: "#ecf0f1".to_string(),
1490        children: basic_types_children,
1491    }
1492}
1493
1494/// Add simple category nodes without subcategories
1495fn add_simple_categories(
1496    treemap_nodes: &mut Vec<TreemapNode>,
1497    basic_types: &[&EnhancedTypeInfo],
1498    smart_pointers_types: &[&EnhancedTypeInfo],
1499    total_memory: usize,
1500) {
1501    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1502    if basic_types_total > 0 {
1503        treemap_nodes.push(TreemapNode {
1504            name: "Basic Types".to_string(),
1505            size: basic_types_total,
1506            percentage: (basic_types_total as f64 / total_memory as f64) * 100.0,
1507            color: "#f39c12".to_string(),
1508            children: Vec::new(),
1509        });
1510    }
1511
1512    let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1513    if smart_pointers_total > 0 {
1514        treemap_nodes.push(TreemapNode {
1515            name: "Smart Pointers".to_string(),
1516            size: smart_pointers_total,
1517            percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1518            color: "#9b59b6".to_string(),
1519            children: Vec::new(),
1520        });
1521    }
1522}
1523
1524/// Add simple category nodes (alternative version)
1525fn add_simple_categories_alt(
1526    treemap_nodes: &mut Vec<TreemapNode>,
1527    collections_types: &[&EnhancedTypeInfo],
1528    smart_pointers_types: &[&EnhancedTypeInfo],
1529    total_memory: usize,
1530) {
1531    let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1532    if collections_total > 0 {
1533        treemap_nodes.push(TreemapNode {
1534            name: "Collections".to_string(),
1535            size: collections_total,
1536            percentage: (collections_total as f64 / total_memory as f64) * 100.0,
1537            color: "#3498db".to_string(),
1538            children: Vec::new(),
1539        });
1540    }
1541
1542    let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1543    if smart_pointers_total > 0 {
1544        treemap_nodes.push(TreemapNode {
1545            name: "Smart Pointers".to_string(),
1546            size: smart_pointers_total,
1547            percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1548            color: "#9b59b6".to_string(),
1549            children: Vec::new(),
1550        });
1551    }
1552}
1553
1554/// Build full layout with all categories
1555fn build_full_layout(
1556    treemap_nodes: &mut Vec<TreemapNode>,
1557    collections_types: &[&EnhancedTypeInfo],
1558    basic_types: &[&EnhancedTypeInfo],
1559    smart_pointers_types: &[&EnhancedTypeInfo],
1560    total_memory: usize,
1561) {
1562    let collections_total: usize = collections_types.iter().map(|t| t.total_size).sum();
1563    if collections_total > 0 {
1564        treemap_nodes.push(build_collections_node(collections_types, total_memory));
1565    }
1566
1567    let basic_types_total: usize = basic_types.iter().map(|t| t.total_size).sum();
1568    if basic_types_total > 0 {
1569        treemap_nodes.push(build_basic_types_node(basic_types, total_memory));
1570    }
1571
1572    let smart_pointers_total: usize = smart_pointers_types.iter().map(|t| t.total_size).sum();
1573    if smart_pointers_total > 0 {
1574        treemap_nodes.push(TreemapNode {
1575            name: "Smart Pointers".to_string(),
1576            size: smart_pointers_total,
1577            percentage: (smart_pointers_total as f64 / total_memory as f64) * 100.0,
1578            color: "#9b59b6".to_string(),
1579            children: Vec::new(),
1580        });
1581    }
1582}
1583
1584/// Render adaptive treemap based on layout strategy
1585fn render_adaptive_treemap(
1586    document: Document,
1587    area: IntegratedTreemapArea,
1588    treemap_data: &[TreemapNode],
1589    _strategy: &TreemapLayoutStrategy,
1590) -> TrackingResult<Document> {
1591    // Use the existing render_squarified_treemap function
1592    render_squarified_treemap(document, area, treemap_data)
1593}
1594
1595/// Render intelligent horizontal-band treemap layout (like basic_usage_graph.svg)
1596fn render_squarified_treemap(
1597    mut document: Document,
1598    area: IntegratedTreemapArea,
1599    nodes: &[TreemapNode],
1600) -> TrackingResult<Document> {
1601    if nodes.is_empty() {
1602        return render_empty_treemap(document, area);
1603    }
1604
1605    // Container background
1606    let container_bg = Rectangle::new()
1607        .set("class", "integrated-treemap-container")
1608        .set("x", area.x)
1609        .set("y", area.y)
1610        .set("width", area.width)
1611        .set("height", area.height)
1612        .set("fill", "#ecf0f1")
1613        .set("stroke", "#bdc3c7")
1614        .set("stroke-width", 2)
1615        .set("rx", 20);
1616    document = document.add(container_bg);
1617
1618    // Calculate total size for proportional height allocation
1619    let total_size: usize = nodes.iter().map(|n| n.size).sum();
1620    if total_size == 0 {
1621        return render_empty_treemap(document, area);
1622    }
1623
1624    // Layout parameters
1625    let padding = 15.0;
1626    let band_spacing = 10.0;
1627    let available_width = area.width - (padding * 2.0);
1628    let available_height =
1629        area.height - (padding * 2.0) - (band_spacing * (nodes.len() - 1) as f64);
1630
1631    let mut current_y = area.y + padding;
1632
1633    // Sort nodes by size (largest first) for better visual hierarchy
1634    let mut sorted_nodes = nodes.to_vec();
1635    sorted_nodes.sort_by(|a, b| b.size.cmp(&a.size));
1636
1637    for node in &sorted_nodes {
1638        // Calculate proportional height for this band
1639        let band_height = (node.size as f64 / total_size as f64) * available_height;
1640
1641        // Render the band based on whether it has children
1642        if !node.children.is_empty() {
1643            document = render_smart_horizontal_band(
1644                document,
1645                area.x + padding,
1646                current_y,
1647                available_width,
1648                band_height,
1649                node,
1650            )?;
1651        } else {
1652            document = render_simple_horizontal_band(
1653                document,
1654                area.x + padding,
1655                current_y,
1656                available_width,
1657                band_height,
1658                node,
1659            )?;
1660        }
1661
1662        current_y += band_height + band_spacing;
1663    }
1664
1665    // Note: No separate legend needed - information is embedded in treemap labels
1666
1667    Ok(document)
1668}
1669
1670/// Render empty treemap when no data available
1671fn render_empty_treemap(
1672    mut document: Document,
1673    area: IntegratedTreemapArea,
1674) -> TrackingResult<Document> {
1675    let container_bg = Rectangle::new()
1676        .set("class", "integrated-treemap-container")
1677        .set("x", area.x)
1678        .set("y", area.y)
1679        .set("width", area.width)
1680        .set("height", area.height)
1681        .set("fill", "#f8f9fa")
1682        .set("stroke", "#dee2e6")
1683        .set("stroke-width", 2)
1684        .set("rx", 20);
1685    document = document.add(container_bg);
1686
1687    let no_data_text = SvgText::new("No Memory Type Data Available")
1688        .set("x", area.x + area.width / 2.0)
1689        .set("y", area.y + area.height / 2.0)
1690        .set("text-anchor", "middle")
1691        .set("font-size", 16)
1692        .set("font-weight", "bold")
1693        .set("fill", "#6c757d");
1694    document = document.add(no_data_text);
1695
1696    Ok(document)
1697}
1698
1699/// Render horizontal band with children (Collections, Basic Types)
1700fn render_smart_horizontal_band(
1701    mut document: Document,
1702    x: f64,
1703    y: f64,
1704    width: f64,
1705    height: f64,
1706    node: &TreemapNode,
1707) -> TrackingResult<Document> {
1708    // Band background
1709    let band_bg = Rectangle::new()
1710        .set("class", "integrated-treemap-rect")
1711        .set("x", x)
1712        .set("y", y)
1713        .set("width", width)
1714        .set("height", height)
1715        .set("fill", node.color.as_str())
1716        .set("rx", 18);
1717    document = document.add(band_bg);
1718
1719    // Band title
1720    let title_y = y + 20.0;
1721    let title = SvgText::new(format!("{} - {:.0}%", node.name, node.percentage))
1722        .set("class", "integrated-treemap-label")
1723        .set("x", x + width / 2.0)
1724        .set("y", title_y)
1725        .set("font-size", 16);
1726    document = document.add(title);
1727
1728    // Children layout area
1729    let children_y = title_y + 10.0;
1730    let children_height = height - 30.0;
1731    let children_padding = 10.0;
1732    let available_children_width = width - (children_padding * 2.0);
1733
1734    // Calculate total children size for proportional width allocation
1735    let total_children_size: usize = node.children.iter().map(|c| c.size).sum();
1736    if total_children_size == 0 {
1737        return Ok(document);
1738    }
1739
1740    let mut current_x = x + children_padding;
1741
1742    for child in &node.children {
1743        // Calculate proportional width for this child
1744        let child_width =
1745            (child.size as f64 / total_children_size as f64) * available_children_width;
1746
1747        // Child rectangle
1748        let child_rect = Rectangle::new()
1749            .set("class", "integrated-treemap-rect")
1750            .set("x", current_x)
1751            .set("y", children_y)
1752            .set("width", child_width - 5.0) // Small gap between children
1753            .set("height", children_height - 10.0)
1754            .set("fill", child.color.as_str())
1755            .set("rx", 18);
1756        document = document.add(child_rect);
1757
1758        // Child label (extract percentage from name if present)
1759        let (child_name, child_relative_percentage) = extract_name_and_percentage(&child.name);
1760
1761        let child_label = SvgText::new(child_name)
1762            .set("class", "integrated-treemap-label")
1763            .set("x", current_x + child_width / 2.0)
1764            .set("y", children_y + children_height / 2.0 - 5.0)
1765            .set("font-size", calculate_font_size(child_width))
1766            .set("font-weight", "bold");
1767        document = document.add(child_label);
1768
1769        // Child percentage
1770        let percentage_text = if let Some(pct) = child_relative_percentage {
1771            format!("({pct})")
1772        } else {
1773            format!(
1774                "({:.0}%)",
1775                (child.size as f64 / total_children_size as f64) * 100.0
1776            )
1777        };
1778
1779        let child_percentage = SvgText::new(percentage_text)
1780            .set("class", "integrated-treemap-percentage")
1781            .set("x", current_x + child_width / 2.0)
1782            .set("y", children_y + children_height / 2.0 + 15.0)
1783            .set("font-size", calculate_font_size(child_width) - 2);
1784        document = document.add(child_percentage);
1785
1786        current_x += child_width;
1787    }
1788
1789    Ok(document)
1790}
1791
1792/// Render simple horizontal band without children (Smart Pointers)
1793fn render_simple_horizontal_band(
1794    mut document: Document,
1795    x: f64,
1796    y: f64,
1797    width: f64,
1798    height: f64,
1799    node: &TreemapNode,
1800) -> TrackingResult<Document> {
1801    // Simple band rectangle
1802    let band_rect = Rectangle::new()
1803        .set("class", "integrated-treemap-rect")
1804        .set("x", x)
1805        .set("y", y)
1806        .set("width", width)
1807        .set("height", height)
1808        .set("fill", node.color.as_str())
1809        .set("rx", 18);
1810    document = document.add(band_rect);
1811
1812    // Band label
1813    let label = SvgText::new(&node.name)
1814        .set("class", "integrated-treemap-label")
1815        .set("x", x + width / 2.0)
1816        .set("y", y + height / 2.0 - 5.0)
1817        .set("font-size", 16)
1818        .set("font-weight", "bold");
1819    document = document.add(label);
1820
1821    // Band percentage
1822    let percentage_label = SvgText::new(format!("{:.0}%", node.percentage))
1823        .set("class", "integrated-treemap-percentage")
1824        .set("x", x + width / 2.0)
1825        .set("y", y + height / 2.0 + 15.0)
1826        .set("font-size", 12);
1827    document = document.add(percentage_label);
1828
1829    Ok(document)
1830}
1831
1832/// Extract name and percentage from formatted strings like "Vec<T> (100%)"
1833fn extract_name_and_percentage(formatted_name: &str) -> (&str, Option<&str>) {
1834    if let Some(open_paren) = formatted_name.find(" (") {
1835        if let Some(close_paren) = formatted_name.find(')') {
1836            let name = &formatted_name[..open_paren];
1837            let percentage = &formatted_name[open_paren + 2..close_paren];
1838            return (name, Some(percentage));
1839        }
1840    }
1841    (formatted_name, None)
1842}
1843
1844/// Calculate appropriate font size based on available width
1845fn calculate_font_size(width: f64) -> i32 {
1846    if width > 200.0 {
1847        15
1848    } else if width > 100.0 {
1849        13
1850    } else if width > 50.0 {
1851        11
1852    } else {
1853        9
1854    }
1855}
1856
1857/// Add categorized allocations visualization
1858pub fn add_categorized_allocations(
1859    mut document: Document,
1860    categories: &[AllocationCategory],
1861) -> TrackingResult<Document> {
1862    let chart_x = 50;
1863    let chart_y = 680; // Adjusted to provide better spacing (320 + 300 + 60 margin)
1864    let chart_width = 850;
1865    let chart_height = 280; // Slightly reduced height
1866
1867    // Chart background with rounded corners
1868    let bg = Rectangle::new()
1869        .set("x", chart_x)
1870        .set("y", chart_y)
1871        .set("width", chart_width)
1872        .set("height", chart_height)
1873        .set("fill", "white")
1874        .set("stroke", "#bdc3c7")
1875        .set("stroke-width", 2)
1876        .set("rx", 20); // Even more rounded corners for natural look
1877
1878    document = document.add(bg);
1879
1880    // Chart title
1881    let title = SvgText::new("Tracked Variables by Category")
1882        .set("x", chart_x + chart_width / 2)
1883        .set("y", chart_y - 10)
1884        .set("text-anchor", "middle")
1885        .set("font-size", 16)
1886        .set("font-weight", "bold")
1887        .set("fill", "#2c3e50");
1888
1889    document = document.add(title);
1890
1891    if categories.is_empty() {
1892        let no_data = SvgText::new("No tracked variables found")
1893            .set("x", chart_x + chart_width / 2)
1894            .set("y", chart_y + chart_height / 2)
1895            .set("text-anchor", "middle")
1896            .set("font-size", 14)
1897            .set("fill", "#7f8c8d");
1898
1899        document = document.add(no_data);
1900        return Ok(document);
1901    }
1902
1903    // Create simple bar chart for categories
1904    let max_size = categories.iter().map(|c| c.total_size).max().unwrap_or(1);
1905    let bar_height = (chart_height - 60) / categories.len().min(8);
1906
1907    for (i, category) in categories.iter().take(8).enumerate() {
1908        let y = chart_y + 30 + i * bar_height;
1909        let bar_width =
1910            ((category.total_size as f64 / max_size as f64) * (chart_width - 200) as f64) as i32;
1911
1912        // Bar with rounded corners
1913        let bar = Rectangle::new()
1914            .set("x", chart_x + 150)
1915            .set("y", y)
1916            .set("width", bar_width)
1917            .set("height", bar_height - 5)
1918            .set("fill", category.color.as_str())
1919            .set("stroke", "#34495e")
1920            .set("stroke-width", 1)
1921            .set("rx", 12); // More rounded bar corners for natural look
1922
1923        document = document.add(bar);
1924
1925        // Category name
1926        let name_text = SvgText::new(&category.name)
1927            .set("x", chart_x + 10)
1928            .set("y", y + bar_height / 2 + 4)
1929            .set("font-size", 12)
1930            .set("font-weight", "600")
1931            .set("fill", "#2c3e50");
1932
1933        document = document.add(name_text);
1934
1935        // Enhanced variable names display - optimize text overflow issues
1936        let var_names: Vec<String> = category
1937            .allocations
1938            .iter()
1939            .filter_map(|a| {
1940                if let Some(var_name) = &a.var_name {
1941                    let type_name = a.type_name.as_deref().unwrap_or("Unknown");
1942                    let (simplified_type, _) = simplify_type_name(type_name);
1943                    // Shorten variable name display to avoid overflow
1944                    let short_var = if var_name.len() > 12 {
1945                        format!("{}...", &var_name[..9])
1946                    } else {
1947                        var_name.to_string()
1948                    };
1949                    Some(format!("{short_var}({simplified_type})"))
1950                } else {
1951                    None
1952                }
1953            })
1954            .take(3) // Reduce number of displayed variables to avoid overflow
1955            .collect();
1956
1957        let mut display_text = if var_names.is_empty() {
1958            format!(
1959                "{} ({} vars)",
1960                format_bytes(category.total_size),
1961                category.allocations.len()
1962            )
1963        } else {
1964            format!(
1965                "{} - Vars: {}",
1966                format_bytes(category.total_size),
1967                var_names.join(", ")
1968            )
1969        };
1970
1971        // Dynamically truncate text to ensure it doesn't exceed chart boundaries
1972        let max_text_width = chart_width - 180; // Reserve margin
1973        let estimated_char_width = 7; // Estimate character width
1974        let max_chars = (max_text_width / estimated_char_width) as usize;
1975
1976        if display_text.len() > max_chars {
1977            display_text = format!("{}...", &display_text[..max_chars.saturating_sub(3)]);
1978        }
1979
1980        let size_text = SvgText::new(display_text)
1981            .set("x", chart_x + 160)
1982            .set("y", y + bar_height / 2 + 4)
1983            .set("font-size", 11) // Slightly reduce font size to avoid overflow
1984            .set("font-weight", "bold")
1985            .set("fill", "#FFFFFF")
1986            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
1987
1988        document = document.add(size_text);
1989    }
1990
1991    Ok(document)
1992}
1993
1994/// Add memory timeline visualization
1995pub fn add_memory_timeline(
1996    mut document: Document,
1997    allocations: &[AllocationInfo],
1998    _stats: &MemoryStats,
1999) -> TrackingResult<Document> {
2000    let chart_x = 50;
2001    let chart_y = 1380; // Moved up from 1780
2002    let chart_width = 1700;
2003    let chart_height = 300;
2004
2005    // Chart background
2006    let bg = Rectangle::new()
2007        .set("x", chart_x)
2008        .set("y", chart_y)
2009        .set("width", chart_width)
2010        .set("height", chart_height)
2011        .set("fill", "white")
2012        .set("stroke", "#bdc3c7")
2013        .set("stroke-width", 1)
2014        .set("rx", 5);
2015
2016    document = document.add(bg);
2017
2018    // Chart title
2019    let title = SvgText::new("Variable Allocation Timeline")
2020        .set("x", chart_x + chart_width / 2)
2021        .set("y", chart_y - 10)
2022        .set("text-anchor", "middle")
2023        .set("font-size", 16)
2024        .set("font-weight", "bold")
2025        .set("fill", "#2c3e50");
2026
2027    document = document.add(title);
2028
2029    if allocations.is_empty() {
2030        let no_data = SvgText::new("No allocation data available")
2031            .set("x", chart_x + chart_width / 2)
2032            .set("y", chart_y + chart_height / 2)
2033            .set("text-anchor", "middle")
2034            .set("font-size", 14)
2035            .set("fill", "#7f8c8d");
2036
2037        document = document.add(no_data);
2038        return Ok(document);
2039    }
2040
2041    // Filter and sort tracked allocations by memory usage and lifecycle
2042    let mut tracked_allocs: Vec<_> = allocations
2043        .iter()
2044        .filter(|a| a.var_name.is_some())
2045        .collect();
2046
2047    // Sort by memory size (descending) and then by lifecycle duration (descending)
2048    tracked_allocs.sort_by(|a, b| {
2049        let size_cmp = b.size.cmp(&a.size);
2050        if size_cmp == std::cmp::Ordering::Equal {
2051            // If sizes are equal, sort by lifecycle duration (timestamp difference as proxy)
2052            let a_duration =
2053                a.timestamp_dealloc.unwrap_or(a.timestamp_alloc + 1000) - a.timestamp_alloc;
2054            let b_duration =
2055                b.timestamp_dealloc.unwrap_or(b.timestamp_alloc + 1000) - b.timestamp_alloc;
2056            b_duration.cmp(&a_duration)
2057        } else {
2058            size_cmp
2059        }
2060    });
2061
2062    if tracked_allocs.is_empty() {
2063        let no_data = SvgText::new("No tracked variables found")
2064            .set("x", chart_x + chart_width / 2)
2065            .set("y", chart_y + chart_height / 2)
2066            .set("text-anchor", "middle")
2067            .set("font-size", 14)
2068            .set("fill", "#7f8c8d");
2069
2070        document = document.add(no_data);
2071        return Ok(document);
2072    }
2073
2074    let min_time = tracked_allocs
2075        .iter()
2076        .map(|a| a.timestamp_alloc)
2077        .min()
2078        .unwrap_or(0);
2079    let max_time = tracked_allocs
2080        .iter()
2081        .map(|a| a.timestamp_alloc)
2082        .max()
2083        .unwrap_or(min_time + 1);
2084    let _time_range = (max_time - min_time).max(1);
2085
2086    // Optimized layout parameters to prevent overflow
2087    let label_width = 500; // More space for variable names
2088    let timeline_width = chart_width - label_width - 80; // Better margin
2089    let max_items = 10; // Show top 10 variables with highest memory usage and longest lifecycles
2090
2091    // Calculate optimal row height based on available space
2092    let available_height = chart_height - 120; // Reserve space for title, axis, and note
2093    let row_height = (available_height / max_items as i32).clamp(18, 22);
2094
2095    // Draw timeline for tracked variables with optimized spacing
2096    for (i, allocation) in tracked_allocs.iter().take(max_items).enumerate() {
2097        // Use time-based positioning for better timeline representation
2098        let time_ratio = if max_time > min_time {
2099            let alloc_time = allocation.timestamp_alloc.max(min_time); // Prevent underflow
2100            (alloc_time - min_time) as f64 / (max_time - min_time) as f64
2101        } else {
2102            0.5 // Center if no time range
2103        };
2104        let x = chart_x + 30 + (time_ratio * timeline_width as f64) as i32;
2105        let y = chart_y + 40 + (i as i32 * row_height);
2106
2107        // Ensure x position stays within timeline bounds
2108        let x = x.min(chart_x + timeline_width - 10).max(chart_x + 30);
2109
2110        // Get color based on type category
2111        let color = if let Some(type_name) = &allocation.type_name {
2112            let (_, category) = simplify_type_name(type_name);
2113            get_category_color(&category)
2114        } else {
2115            "#95a5a6".to_string()
2116        };
2117
2118        // Draw allocation point
2119        let point = Circle::new()
2120            .set("cx", x)
2121            .set("cy", y)
2122            .set("r", 5)
2123            .set("fill", color)
2124            .set("stroke", "#2c3e50")
2125            .set("stroke-width", 2);
2126
2127        document = document.add(point);
2128
2129        // Draw connecting line to label area with better positioning
2130        let label_start_x = chart_x + timeline_width + 40;
2131        let line = svg::node::element::Line::new()
2132            .set("x1", x + 5)
2133            .set("y1", y)
2134            .set("x2", label_start_x - 5)
2135            .set("y2", y)
2136            .set("stroke", "#bdc3c7")
2137            .set("stroke-width", 1)
2138            .set("stroke-dasharray", "2,3");
2139
2140        document = document.add(line);
2141
2142        // Optimized variable label with smart truncation to prevent overflow
2143        if let Some(var_name) = &allocation.var_name {
2144            let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
2145            let (simplified_type, _) = simplify_type_name(type_name);
2146            let size_info = format_bytes(allocation.size);
2147
2148            // Smart truncation based on available space
2149            let max_var_name_length = 20;
2150            let max_type_length = 12;
2151
2152            let truncated_var_name = if var_name.len() > max_var_name_length {
2153                format!("{}...", &var_name[..max_var_name_length - 3])
2154            } else {
2155                var_name.to_string()
2156            };
2157
2158            let truncated_type = if simplified_type.len() > max_type_length {
2159                format!("{}...", &simplified_type[..max_type_length - 3])
2160            } else {
2161                simplified_type
2162            };
2163
2164            let label_text = format!("{truncated_var_name} ({truncated_type}) {size_info}");
2165
2166            let label = SvgText::new(label_text)
2167                .set("x", label_start_x)
2168                .set("y", y + 4)
2169                .set("font-size", 9) // Smaller font for better fit
2170                .set("font-weight", "500")
2171                .set("fill", "#2c3e50");
2172
2173            document = document.add(label);
2174        }
2175    }
2176
2177    // Add timeline axis
2178    let axis_y = chart_y + chart_height - 40;
2179    let axis_line = svg::node::element::Line::new()
2180        .set("x1", chart_x + 20)
2181        .set("y1", axis_y)
2182        .set("x2", chart_x + timeline_width)
2183        .set("y2", axis_y)
2184        .set("stroke", "#34495e")
2185        .set("stroke-width", 2);
2186
2187    document = document.add(axis_line);
2188
2189    // Add axis labels
2190    let start_label = SvgText::new("Timeline")
2191        .set("x", chart_x + 20)
2192        .set("y", axis_y + 20)
2193        .set("font-size", 12)
2194        .set("font-weight", "600")
2195        .set("fill", "#7f8c8d");
2196
2197    document = document.add(start_label);
2198
2199    // Add note about data limitation
2200    let total_tracked = tracked_allocs.len();
2201    if total_tracked > max_items {
2202        let note_text = format!("Note: Showing top {max_items} variables with highest memory usage and longest lifecycles (out of {total_tracked} total tracked variables)");
2203        let note = SvgText::new(note_text)
2204            .set("x", chart_x + 20)
2205            .set("y", chart_y + chart_height - 10)
2206            .set("font-size", 11)
2207            .set("font-style", "italic")
2208            .set("fill", "#7f8c8d");
2209        document = document.add(note);
2210    }
2211
2212    Ok(document)
2213}
2214
2215/// Add fragmentation analysis chart
2216pub fn add_fragmentation_analysis(
2217    mut document: Document,
2218    allocations: &[AllocationInfo],
2219) -> TrackingResult<Document> {
2220    let chart_x = 950;
2221    let chart_y = 320; // Fix: adjust position to avoid overlap with header
2222    let chart_width = 750; // Fix: adapt to new 1600px width
2223    let chart_height = 300;
2224
2225    // Chart background
2226    let bg = Rectangle::new()
2227        .set("x", chart_x)
2228        .set("y", chart_y)
2229        .set("width", chart_width)
2230        .set("height", chart_height)
2231        .set("fill", "white")
2232        .set("stroke", "#f39c12")
2233        .set("stroke-width", 2)
2234        .set("rx", 10);
2235
2236    document = document.add(bg);
2237
2238    // Chart title
2239    let title = SvgText::new("Memory Fragmentation Analysis")
2240        .set("x", chart_x + chart_width / 2)
2241        .set("y", chart_y - 10)
2242        .set("text-anchor", "middle")
2243        .set("font-size", 18)
2244        .set("font-weight", "bold")
2245        .set("fill", "#2c3e50");
2246
2247    document = document.add(title);
2248
2249    if allocations.is_empty() {
2250        let no_data = SvgText::new("No allocation data available")
2251            .set("x", chart_x + chart_width / 2)
2252            .set("y", chart_y + chart_height / 2)
2253            .set("text-anchor", "middle")
2254            .set("font-size", 14)
2255            .set("fill", "#7f8c8d");
2256
2257        document = document.add(no_data);
2258        return Ok(document);
2259    }
2260
2261    // Create size distribution histogram
2262    let size_buckets = [
2263        (0, 64, "Tiny (0-64B)"),
2264        (65, 256, "Small (65-256B)"),
2265        (257, 1024, "Medium (257B-1KB)"),
2266        (1025, 4096, "Large (1-4KB)"),
2267        (4097, 16384, "XLarge (4-16KB)"),
2268        (16385, usize::MAX, "Huge (>16KB)"),
2269    ];
2270
2271    let mut bucket_counts = vec![0; size_buckets.len()];
2272
2273    for allocation in allocations {
2274        for (i, &(min, max, _)) in size_buckets.iter().enumerate() {
2275            if allocation.size >= min && allocation.size <= max {
2276                bucket_counts[i] += 1;
2277                break;
2278            }
2279        }
2280    }
2281
2282    let max_count = bucket_counts.iter().max().copied().unwrap_or(1);
2283    let bar_width = (chart_width - 100) / size_buckets.len();
2284
2285    // Draw histogram bars
2286    for (i, (&(_, _, label), &count)) in size_buckets.iter().zip(bucket_counts.iter()).enumerate() {
2287        let x = chart_x + 50 + i * bar_width;
2288        let bar_height = if max_count > 0 {
2289            (count as f64 / max_count as f64 * (chart_height - 80) as f64) as i32
2290        } else {
2291            0
2292        };
2293        let y = chart_y + chart_height - 40 - bar_height;
2294
2295        // Color based on fragmentation level
2296        let color = match i {
2297            0..=1 => "#27ae60", // Green for small allocations
2298            2..=3 => "#f39c12", // Orange for medium
2299            _ => "#e74c3c",     // Red for large (potential fragmentation)
2300        };
2301
2302        let bar = Rectangle::new()
2303            .set("x", x)
2304            .set("y", y)
2305            .set("width", bar_width - 5)
2306            .set("height", bar_height)
2307            .set("fill", color)
2308            .set("stroke", "#2c3e50")
2309            .set("stroke-width", 1);
2310
2311        document = document.add(bar);
2312
2313        // Count label
2314        let count_text = SvgText::new(count.to_string())
2315            .set("x", x + bar_width / 2)
2316            .set("y", y - 5)
2317            .set("text-anchor", "middle")
2318            .set("font-size", 12)
2319            .set("font-weight", "bold")
2320            .set("fill", "#2c3e50");
2321
2322        document = document.add(count_text);
2323
2324        // Size label
2325        let size_text = SvgText::new(label)
2326            .set("x", x + bar_width / 2)
2327            .set("y", chart_y + chart_height - 10)
2328            .set("text-anchor", "middle")
2329            .set("font-size", 10)
2330            .set("fill", "#7f8c8d");
2331
2332        document = document.add(size_text);
2333    }
2334
2335    Ok(document)
2336}
2337
2338/// Add call stack analysis visualization
2339pub fn add_callstack_analysis(
2340    mut document: Document,
2341    allocations: &[AllocationInfo],
2342) -> TrackingResult<Document> {
2343    let chart_x = 950;
2344    let chart_y = 680; // Fix: adjust position to avoid overlap
2345    let chart_width = 750; // Fix: adapt to new 1600px width
2346    let chart_height = 300;
2347
2348    // Chart background
2349    let bg = Rectangle::new()
2350        .set("x", chart_x)
2351        .set("y", chart_y)
2352        .set("width", chart_width)
2353        .set("height", chart_height)
2354        .set("fill", "white")
2355        .set("stroke", "#9b59b6")
2356        .set("stroke-width", 2)
2357        .set("rx", 10);
2358
2359    document = document.add(bg);
2360
2361    // Chart title
2362    let title = SvgText::new("Call Stack Analysis")
2363        .set("x", chart_x + chart_width / 2)
2364        .set("y", chart_y - 10)
2365        .set("text-anchor", "middle")
2366        .set("font-size", 18)
2367        .set("font-weight", "bold")
2368        .set("fill", "#2c3e50");
2369
2370    document = document.add(title);
2371
2372    // Group allocations by variable name and type with better categorization
2373    let mut source_stats: HashMap<String, (usize, usize)> = HashMap::new();
2374
2375    for allocation in allocations {
2376        // Create a more descriptive key that helps identify allocations
2377        let source_key = if let Some(var_name) = &allocation.var_name {
2378            // Tracked variables - show variable name and type
2379            if let Some(type_name) = &allocation.type_name {
2380                let (simplified_type, _) = simplify_type_name(type_name);
2381                format!(
2382                    "{}({}) memory: {}",
2383                    var_name,
2384                    simplified_type,
2385                    format_bytes(allocation.size)
2386                )
2387            } else {
2388                format!(
2389                    "{}(Unknown Type) memory: {}",
2390                    var_name,
2391                    format_bytes(allocation.size)
2392                )
2393            }
2394        } else if let Some(type_name) = &allocation.type_name {
2395            // Untracked allocations with known type - categorize by type and source
2396            let (simplified_type, _) = simplify_type_name(type_name);
2397
2398            // Try to identify the source of untracked allocations
2399            if type_name.contains("std::") || type_name.contains("alloc::") {
2400                format!("System/Runtime {simplified_type} (untracked)")
2401            } else if simplified_type.contains("Vec") {
2402                "Internal Vec allocations (untracked)".to_string()
2403            } else if simplified_type.contains("String") {
2404                "Internal String allocations (untracked)".to_string()
2405            } else if simplified_type.contains("HashMap") {
2406                "Internal HashMap allocations (untracked)".to_string()
2407            } else {
2408                format!("Internal {simplified_type} allocations (untracked)")
2409            }
2410        } else {
2411            // Completely unknown allocations
2412            "System/Runtime allocations (no type info)".to_string()
2413        };
2414
2415        let entry = source_stats.entry(source_key).or_insert((0, 0));
2416        entry.0 += 1;
2417        entry.1 += allocation.size;
2418    }
2419
2420    if source_stats.is_empty() {
2421        let no_data = SvgText::new("No call stack data available")
2422            .set("x", chart_x + chart_width / 2)
2423            .set("y", chart_y + chart_height / 2)
2424            .set("text-anchor", "middle")
2425            .set("font-size", 14)
2426            .set("fill", "#7f8c8d");
2427
2428        document = document.add(no_data);
2429        return Ok(document);
2430    }
2431
2432    // Sort by total size and take top 10
2433    let mut sorted_sources: Vec<_> = source_stats.iter().collect();
2434    sorted_sources.sort_by(|a, b| b.1 .1.cmp(&a.1 .1));
2435
2436    let max_size = sorted_sources
2437        .first()
2438        .map(|(_, (_, size))| *size)
2439        .unwrap_or(1);
2440
2441    // Draw tree-like visualization
2442    for (i, (source, (count, total_size))) in sorted_sources.iter().take(10).enumerate() {
2443        let y = chart_y + 40 + i * 25;
2444        let node_size = ((*total_size as f64 / max_size as f64) * 15.0 + 5.0) as i32;
2445
2446        // Draw node
2447        let colors = ["#e74c3c", "#f39c12", "#27ae60", "#3498db", "#9b59b6"];
2448        let color = colors[i % colors.len()];
2449
2450        let node = Circle::new()
2451            .set("cx", chart_x + 50)
2452            .set("cy", y)
2453            .set("r", node_size)
2454            .set("fill", color)
2455            .set("stroke", "#2c3e50")
2456            .set("stroke-width", 2);
2457
2458        document = document.add(node);
2459
2460        // Source label
2461        let source_text = format!("{source} ({count} allocs, {total_size} bytes)");
2462
2463        let label = SvgText::new(source_text)
2464            .set("x", chart_x + 80)
2465            .set("y", y + 5)
2466            .set("font-size", 11)
2467            .set("font-weight", "500")
2468            .set("fill", "#2c3e50");
2469
2470        document = document.add(label);
2471    }
2472
2473    Ok(document)
2474}
2475
2476/// Add memory growth trends visualization
2477pub fn add_memory_growth_trends(
2478    mut document: Document,
2479    allocations: &[AllocationInfo],
2480    stats: &MemoryStats,
2481) -> TrackingResult<Document> {
2482    let chart_x = 50;
2483    let chart_y = 1040; // Moved up from 1430
2484    let chart_width = 1700;
2485    let chart_height = 300;
2486
2487    // Chart background
2488    let bg = Rectangle::new()
2489        .set("x", chart_x)
2490        .set("y", chart_y)
2491        .set("width", chart_width)
2492        .set("height", chart_height)
2493        .set("fill", "white")
2494        .set("stroke", "#27ae60")
2495        .set("stroke-width", 2)
2496        .set("rx", 10);
2497
2498    document = document.add(bg);
2499
2500    // Chart title
2501    let title = SvgText::new("Memory Growth Trends")
2502        .set("x", chart_x + chart_width / 2)
2503        .set("y", chart_y - 10)
2504        .set("text-anchor", "middle")
2505        .set("font-size", 18)
2506        .set("font-weight", "bold")
2507        .set("fill", "#2c3e50");
2508
2509    document = document.add(title);
2510
2511    if allocations.is_empty() {
2512        let no_data = SvgText::new("No allocation data available")
2513            .set("x", chart_x + chart_width / 2)
2514            .set("y", chart_y + chart_height / 2)
2515            .set("text-anchor", "middle")
2516            .set("font-size", 14)
2517            .set("fill", "#7f8c8d");
2518
2519        document = document.add(no_data);
2520        return Ok(document);
2521    }
2522
2523    // Create simplified trend visualization
2524    let time_points = 10;
2525    let point_width = (chart_width - 100) / time_points;
2526
2527    for i in 0..time_points {
2528        let x = chart_x + 50 + i * point_width;
2529        let simulated_memory = stats.active_memory / time_points * (i + 1);
2530        let y = chart_y + chart_height
2531            - 50
2532            - ((simulated_memory as f64 / stats.peak_memory as f64) * (chart_height - 100) as f64)
2533                as i32;
2534
2535        // Draw data points
2536        let point = Circle::new()
2537            .set("cx", x)
2538            .set("cy", y)
2539            .set("r", 4)
2540            .set("fill", "#27ae60")
2541            .set("stroke", "#2c3e50")
2542            .set("stroke-width", 1);
2543
2544        document = document.add(point);
2545
2546        // Connect with lines
2547        if i > 0 {
2548            let prev_x = chart_x + 50 + (i - 1) * point_width;
2549            let prev_memory = stats.active_memory / time_points * i;
2550            let prev_y = chart_y + chart_height
2551                - 50
2552                - ((prev_memory as f64 / stats.peak_memory as f64) * (chart_height - 100) as f64)
2553                    as i32;
2554
2555            let line = svg::node::element::Line::new()
2556                .set("x1", prev_x)
2557                .set("y1", prev_y)
2558                .set("x2", x)
2559                .set("y2", y)
2560                .set("stroke", "#27ae60")
2561                .set("stroke-width", 2);
2562
2563            document = document.add(line);
2564        }
2565    }
2566
2567    // Add peak memory indicator
2568    let peak_y = chart_y + 50;
2569    let peak_line = svg::node::element::Line::new()
2570        .set("x1", chart_x + 50)
2571        .set("y1", peak_y)
2572        .set("x2", chart_x + chart_width - 50)
2573        .set("y2", peak_y)
2574        .set("stroke", "#e74c3c")
2575        .set("stroke-width", 2)
2576        .set("stroke-dasharray", "10,5");
2577
2578    document = document.add(peak_line);
2579
2580    let peak_label = SvgText::new(format!("Peak: {} bytes", stats.peak_memory))
2581        .set("x", chart_x + chart_width - 100)
2582        .set("y", peak_y - 10)
2583        .set("font-size", 12)
2584        .set("font-weight", "bold")
2585        .set("fill", "#e74c3c");
2586
2587    document = document.add(peak_label);
2588
2589    Ok(document)
2590}
2591
2592/// Add interactive legend
2593pub fn add_interactive_legend(mut document: Document) -> TrackingResult<Document> {
2594    let legend_x = 50;
2595    let legend_y = 1720; // Moved up from 2130
2596    let legend_width = 850;
2597    let legend_height = 250;
2598
2599    // Legend background
2600    let bg = Rectangle::new()
2601        .set("x", legend_x)
2602        .set("y", legend_y)
2603        .set("width", legend_width)
2604        .set("height", legend_height)
2605        .set("fill", "white")
2606        .set("stroke", "#34495e")
2607        .set("stroke-width", 2)
2608        .set("rx", 10);
2609
2610    document = document.add(bg);
2611
2612    // Legend title
2613    let title = SvgText::new("Interactive Legend & Guide")
2614        .set("x", legend_x + legend_width / 2)
2615        .set("y", legend_y - 10)
2616        .set("text-anchor", "middle")
2617        .set("font-size", 18)
2618        .set("font-weight", "bold")
2619        .set("fill", "#2c3e50");
2620
2621    document = document.add(title);
2622
2623    // Legend items
2624    let legend_items = [
2625        ("#e74c3c", "High Memory Usage / Critical"),
2626        ("#f39c12", "Medium Usage / Warning"),
2627        ("#27ae60", "Low Usage / Good"),
2628        ("#3498db", "Performance Metrics"),
2629        ("#9b59b6", "Call Stack Data"),
2630        ("#34495e", "General Information"),
2631    ];
2632
2633    for (i, (color, description)) in legend_items.iter().enumerate() {
2634        let x = legend_x + 30 + (i % 3) * 220;
2635        let y = legend_y + 40 + (i / 3) * 40;
2636
2637        // Color swatch
2638        let swatch = Rectangle::new()
2639            .set("x", x)
2640            .set("y", y - 10)
2641            .set("width", 20)
2642            .set("height", 15)
2643            .set("fill", *color)
2644            .set("stroke", "#2c3e50")
2645            .set("stroke-width", 1);
2646
2647        document = document.add(swatch);
2648
2649        // Description
2650        let desc_text = SvgText::new(*description)
2651            .set("x", x + 30)
2652            .set("y", y)
2653            .set("font-size", 12)
2654            .set("fill", "#2c3e50");
2655
2656        document = document.add(desc_text);
2657    }
2658
2659    Ok(document)
2660}
2661
2662/// Add comprehensive summary
2663pub fn add_comprehensive_summary(
2664    mut document: Document,
2665    stats: &MemoryStats,
2666    allocations: &[AllocationInfo],
2667) -> TrackingResult<Document> {
2668    let summary_x = 950;
2669    let summary_y = 1720; // Moved up from 2130
2670    let summary_width = 800;
2671    let summary_height = 250;
2672
2673    // Summary background
2674    let bg = Rectangle::new()
2675        .set("x", summary_x)
2676        .set("y", summary_y)
2677        .set("width", summary_width)
2678        .set("height", summary_height)
2679        .set("fill", "white")
2680        .set("stroke", "#2c3e50")
2681        .set("stroke-width", 2)
2682        .set("rx", 10);
2683
2684    document = document.add(bg);
2685
2686    // Summary title
2687    let title = SvgText::new("Memory Analysis Summary")
2688        .set("x", summary_x + summary_width / 2)
2689        .set("y", summary_y - 10)
2690        .set("text-anchor", "middle")
2691        .set("font-size", 18)
2692        .set("font-weight", "bold")
2693        .set("fill", "#2c3e50");
2694
2695    document = document.add(title);
2696
2697    // Calculate summary metrics
2698    let tracked_vars = allocations.iter().filter(|a| a.var_name.is_some()).count();
2699    let avg_size = if !allocations.is_empty() {
2700        allocations.iter().map(|a| a.size).sum::<usize>() / allocations.len()
2701    } else {
2702        0
2703    };
2704
2705    let summary_items = [
2706        format!("Total Active Allocations: {}", stats.active_allocations),
2707        format!(
2708            "Tracked Variables: {} ({:.1}%)",
2709            tracked_vars,
2710            if stats.active_allocations > 0 {
2711                tracked_vars as f64 / stats.active_allocations as f64 * 100.0
2712            } else {
2713                0.0
2714            }
2715        ),
2716        format!("Average Allocation Size: {avg_size} bytes"),
2717        format!(
2718            "Memory Efficiency: {:.1}%",
2719            if stats.total_allocations > 0 {
2720                stats.total_deallocations as f64 / stats.total_allocations as f64 * 100.0
2721            } else {
2722                0.0
2723            }
2724        ),
2725        format!(
2726            "Peak vs Current: {} vs {} bytes",
2727            stats.peak_memory, stats.active_memory
2728        ),
2729    ];
2730
2731    for (i, item) in summary_items.iter().enumerate() {
2732        let summary_text = SvgText::new(item)
2733            .set("x", summary_x + 30)
2734            .set("y", summary_y + 40 + i * 25)
2735            .set("font-size", 13)
2736            .set("font-weight", "500")
2737            .set("fill", "#2c3e50");
2738
2739        document = document.add(summary_text);
2740    }
2741
2742    Ok(document)
2743}
2744
2745/// Add CSS styles for interactive elements
2746/// Add enhanced memory allocation timeline with multiple visualization options
2747pub fn add_enhanced_timeline_dashboard(
2748    mut document: Document,
2749    stats: &MemoryStats,
2750    allocations: &[AllocationInfo],
2751) -> TrackingResult<Document> {
2752    // First, add the performance dashboard with circular progress indicators
2753    let dashboard_y = 120;
2754    let dashboard_height = 200;
2755
2756    // Calculate performance metrics
2757    let memory_efficiency = if stats.peak_memory > 0 {
2758        (stats.active_memory as f64 / stats.peak_memory as f64) * 100.0
2759    } else {
2760        0.0
2761    };
2762
2763    let allocation_efficiency = if stats.total_allocations > 0 {
2764        (stats.active_allocations as f64 / stats.total_allocations as f64) * 100.0
2765    } else {
2766        0.0
2767    };
2768
2769    let fragmentation_ratio = if stats.total_allocated > 0 {
2770        (1.0 - (stats.active_memory as f64 / stats.total_allocated as f64)) * 100.0
2771    } else {
2772        0.0
2773    };
2774
2775    // Performance Dashboard Section Background
2776    let dashboard_bg = Rectangle::new()
2777        .set("x", 50)
2778        .set("y", dashboard_y)
2779        .set("width", 1700)
2780        .set("height", dashboard_height)
2781        .set("fill", "rgba(255,255,255,0.1)")
2782        .set("stroke", "rgba(255,255,255,0.2)")
2783        .set("stroke-width", 1)
2784        .set("rx", 12);
2785    document = document.add(dashboard_bg);
2786
2787    // Dashboard Title
2788    let dashboard_title = SvgText::new("Performance Dashboard")
2789        .set("x", 900)
2790        .set("y", dashboard_y + 25)
2791        .set("text-anchor", "middle")
2792        .set("font-size", 18)
2793        .set("font-weight", "bold")
2794        .set("fill", "#FFFFFF");
2795    document = document.add(dashboard_title);
2796
2797    // Create circular progress indicators
2798    let metrics = [
2799        ("Active Memory", memory_efficiency, "#3498db"),
2800        ("Allocation Efficiency", allocation_efficiency, "#2ecc71"),
2801        (
2802            "Memory Fragmentation",
2803            100.0 - fragmentation_ratio,
2804            "#e74c3c",
2805        ),
2806        (
2807            "Peak Usage",
2808            if stats.peak_memory > 0 {
2809                (stats.active_memory as f64 / stats.peak_memory as f64) * 100.0
2810            } else {
2811                0.0
2812            },
2813            "#f39c12",
2814        ),
2815    ];
2816
2817    for (i, (label, percentage, color)) in metrics.iter().enumerate() {
2818        let x = 200 + i * 350;
2819        let y = dashboard_y + 100;
2820        let radius = 40;
2821
2822        // Background circle
2823        let bg_circle = Circle::new()
2824            .set("cx", x)
2825            .set("cy", y)
2826            .set("r", radius)
2827            .set("fill", "none")
2828            .set("stroke", "rgba(255,255,255,0.2)")
2829            .set("stroke-width", 8);
2830        document = document.add(bg_circle);
2831
2832        // Progress circle
2833        let circumference = 2.0 * std::f64::consts::PI * radius as f64;
2834        let progress = circumference * (percentage / 100.0);
2835        let dash_array = format!("{} {}", progress, circumference - progress);
2836
2837        let progress_circle = Circle::new()
2838            .set("cx", x)
2839            .set("cy", y)
2840            .set("r", radius)
2841            .set("fill", "none")
2842            .set("stroke", *color)
2843            .set("stroke-width", 8)
2844            .set("stroke-dasharray", dash_array)
2845            .set("stroke-dashoffset", 0)
2846            .set("transform", format!("rotate(-90 {x} {y})"))
2847            .set("stroke-linecap", "round");
2848        document = document.add(progress_circle);
2849
2850        // Percentage text
2851        let percentage_text = SvgText::new(format!("{percentage:.0}%"))
2852            .set("x", x)
2853            .set("y", y + 5)
2854            .set("text-anchor", "middle")
2855            .set("font-size", 16)
2856            .set("font-weight", "bold")
2857            .set("fill", "#FFFFFF");
2858        document = document.add(percentage_text);
2859
2860        // Label
2861        let label_text = SvgText::new(*label)
2862            .set("x", x)
2863            .set("y", y + 65)
2864            .set("text-anchor", "middle")
2865            .set("font-size", 12)
2866            .set("fill", "#FFFFFF");
2867        document = document.add(label_text);
2868    }
2869
2870    // Now add the timeline chart below the dashboard
2871    let chart_x = 50;
2872    let chart_y = 350; // Move down to avoid overlapping with dashboard
2873    let chart_width = 1700;
2874    let chart_height = 350; // Reduce height to avoid being blocked by modules below
2875
2876    // Chart background
2877    let bg = Rectangle::new()
2878        .set("x", chart_x)
2879        .set("y", chart_y)
2880        .set("width", chart_width)
2881        .set("height", chart_height)
2882        .set("fill", "white")
2883        .set("stroke", "#bdc3c7")
2884        .set("stroke-width", 1)
2885        .set("rx", 8);
2886
2887    document = document.add(bg);
2888
2889    // Chart title
2890    let title = SvgText::new("Memory Allocation Timeline")
2891        .set("x", chart_x + chart_width / 2)
2892        .set("y", chart_y - 15)
2893        .set("text-anchor", "middle")
2894        .set("font-size", 16)
2895        .set("font-weight", "bold")
2896        .set("fill", "#2c3e50");
2897
2898    document = document.add(title);
2899
2900    // Group tracked variables by scope for better visualization
2901    let tracked_vars: Vec<&AllocationInfo> = allocations
2902        .iter()
2903        .filter(|a| a.var_name.is_some())
2904        .collect();
2905
2906    if tracked_vars.is_empty() {
2907        let no_data = SvgText::new("No tracked variables found for timeline visualization")
2908            .set("x", chart_x + chart_width / 2)
2909            .set("y", chart_y + chart_height / 2)
2910            .set("text-anchor", "middle")
2911            .set("font-size", 14)
2912            .set("fill", "#7f8c8d");
2913        document = document.add(no_data);
2914        return Ok(document);
2915    }
2916
2917    // Sort tracked variables by memory size and lifecycle duration, then limit to top 10
2918    let mut sorted_tracked_vars: Vec<_> = tracked_vars.clone();
2919    sorted_tracked_vars.sort_by(|a, b| {
2920        let size_cmp = b.size.cmp(&a.size);
2921        if size_cmp == std::cmp::Ordering::Equal {
2922            // If sizes are equal, sort by lifecycle duration (timestamp difference as proxy)
2923            let a_duration =
2924                a.timestamp_dealloc.unwrap_or(a.timestamp_alloc + 1000) - a.timestamp_alloc;
2925            let b_duration =
2926                b.timestamp_dealloc.unwrap_or(b.timestamp_alloc + 1000) - b.timestamp_alloc;
2927            b_duration.cmp(&a_duration)
2928        } else {
2929            size_cmp
2930        }
2931    });
2932
2933    // Limit to top 10 variables to prevent overflow
2934    let max_vars_to_show = 10;
2935    let limited_tracked_vars: Vec<_> = sorted_tracked_vars
2936        .into_iter()
2937        .take(max_vars_to_show)
2938        .collect();
2939
2940    // Group limited variables by scope (extract scope from variable names)
2941    let mut scope_groups: std::collections::HashMap<String, Vec<&AllocationInfo>> =
2942        std::collections::HashMap::new();
2943    for var in &limited_tracked_vars {
2944        let scope_name = extract_scope_name(
2945            var.var_name
2946                .as_ref()
2947                .unwrap_or(&"unknown_variable".to_string()),
2948        );
2949        scope_groups.entry(scope_name).or_default().push(*var);
2950    }
2951
2952    // Sort scopes for consistent display
2953    let mut sorted_scopes: Vec<_> = scope_groups.into_iter().collect();
2954    sorted_scopes.sort_by(|a, b| a.0.cmp(&b.0));
2955
2956    // Calculate time ranges for limited tracked variables
2957    let max_time = limited_tracked_vars
2958        .iter()
2959        .map(|a| a.timestamp_alloc)
2960        .max()
2961        .unwrap_or(1000) as f64;
2962    let min_time = limited_tracked_vars
2963        .iter()
2964        .map(|a| a.timestamp_alloc)
2965        .min()
2966        .unwrap_or(0) as f64;
2967    let time_range = (max_time - min_time).max(1.0);
2968
2969    // Plot area dimensions - Gantt chart layout
2970    let plot_x = chart_x + 200; // More space for variable names
2971    let plot_y = chart_y + 50;
2972    let plot_width = chart_width - 350; // Space for names and legend
2973    let plot_height = chart_height - 100;
2974
2975    let row_height = (plot_height as f64 / sorted_scopes.len().max(1) as f64).clamp(25.0, 40.0);
2976
2977    // Time axis (horizontal)
2978    let time_axis = svg::node::element::Line::new()
2979        .set("x1", plot_x)
2980        .set("y1", plot_y + plot_height)
2981        .set("x2", plot_x + plot_width)
2982        .set("y2", plot_y + plot_height)
2983        .set("stroke", "#34495e")
2984        .set("stroke-width", 2);
2985    document = document.add(time_axis);
2986
2987    // Add time labels with better formatting - use relative time units
2988    let time_units = ["0ms", "0.25ms", "0.5ms", "0.75ms", "1ms"];
2989    for (i, item) in time_units.iter().enumerate() {
2990        let x = plot_x + (i * plot_width / 4);
2991
2992        let tick = svg::node::element::Line::new()
2993            .set("x1", x)
2994            .set("y1", plot_y + plot_height)
2995            .set("x2", x)
2996            .set("y2", plot_y + plot_height + 5)
2997            .set("stroke", "#34495e")
2998            .set("stroke-width", 1);
2999        document = document.add(tick);
3000
3001        // Better formatted time labels
3002        let time_label = item;
3003        let label = SvgText::new(*time_label)
3004            .set("x", x)
3005            .set("y", plot_y + plot_height + 18)
3006            .set("text-anchor", "middle")
3007            .set("font-size", 10)
3008            .set("font-weight", "500")
3009            .set("fill", "#2c3e50");
3010        document = document.add(label);
3011    }
3012
3013    // X-axis label - clearly indicate horizontal axis meaning
3014    let x_label = SvgText::new("Execution Time (milliseconds)")
3015        .set("x", plot_x + plot_width / 2)
3016        .set("y", plot_y + plot_height + 40)
3017        .set("text-anchor", "middle")
3018        .set("font-size", 12)
3019        .set("font-weight", "bold")
3020        .set("fill", "#2c3e50");
3021    document = document.add(x_label);
3022
3023    // Remove Y-axis label to save space and reduce clutter
3024
3025    // Add Y-axis scale markers for better readability
3026    let scale_markers = [
3027        (16, "16B"),
3028        (256, "256B"),
3029        (1024, "1KB"),
3030        (4096, "4KB"),
3031        (16384, "16KB"),
3032    ];
3033
3034    for (size, label) in scale_markers.iter() {
3035        // Skip size filtering for now - just show all scale markers
3036        {
3037            let log_size = (*size as f64).ln();
3038            let log_min = 1.0_f64.ln();
3039            let log_max = 16384.0_f64.ln();
3040            let log_range = log_max - log_min;
3041
3042            if log_range > 0.0 {
3043                let y_pos = plot_y + plot_height
3044                    - ((log_size - log_min) / log_range * plot_height as f64) as i32;
3045
3046                // Scale marker line
3047                let marker_line = svg::node::element::Line::new()
3048                    .set("x1", plot_x - 5)
3049                    .set("y1", y_pos)
3050                    .set("x2", plot_x + 5)
3051                    .set("y2", y_pos)
3052                    .set("stroke", "#7f8c8d")
3053                    .set("stroke-width", 1);
3054                document = document.add(marker_line);
3055
3056                // Scale marker label
3057                let marker_label = SvgText::new(*label)
3058                    .set("x", plot_x - 15)
3059                    .set("y", y_pos + 3)
3060                    .set("text-anchor", "end")
3061                    .set("font-size", 10)
3062                    .set("fill", "#7f8c8d");
3063                document = document.add(marker_label);
3064            }
3065        }
3066    }
3067
3068    // Add X-axis time scale markers with better formatting
3069    let time_markers = 5;
3070    for i in 0..=time_markers {
3071        let time_point = min_time + (time_range * i as f64 / time_markers as f64);
3072        let x_pos = plot_x + (i * plot_width / time_markers);
3073
3074        // Time marker line
3075        let time_marker_line = svg::node::element::Line::new()
3076            .set("x1", x_pos)
3077            .set("y1", plot_y + plot_height - 5)
3078            .set("x2", x_pos)
3079            .set("y2", plot_y + plot_height + 5)
3080            .set("stroke", "#7f8c8d")
3081            .set("stroke-width", 1);
3082        document = document.add(time_marker_line);
3083
3084        // Format time more readably
3085        let formatted_time = if time_point < 1000.0 {
3086            format!("{time_point:.1}ms")
3087        } else if time_point < 60000.0 {
3088            format!("{:.2}s", time_point / 1000.0)
3089        } else {
3090            format!("{:.1}m", time_point / 60000.0)
3091        };
3092
3093        let time_label = SvgText::new(formatted_time)
3094            .set("x", x_pos)
3095            .set("y", plot_y + plot_height + 20)
3096            .set("text-anchor", "middle")
3097            .set("font-size", 10)
3098            .set("font-weight", "500")
3099            .set("fill", "#2c3e50");
3100        document = document.add(time_label);
3101    }
3102
3103    // Categorize allocations and get colors
3104    let categorized = categorize_allocations(allocations);
3105    let mut category_colors: HashMap<String, String> = HashMap::new();
3106    for category in &categorized {
3107        category_colors.insert(category.name.clone(), category.color.clone());
3108    }
3109
3110    // Calculate P95 threshold for larger dots
3111    let mut sizes: Vec<usize> = allocations
3112        .iter()
3113        .map(|a| a.size)
3114        .filter(|&s| s > 0)
3115        .collect();
3116    sizes.sort_unstable();
3117    let _p95_threshold = if !sizes.is_empty() {
3118        let p95_index = (sizes.len() as f64 * 0.95) as usize;
3119        sizes[p95_index.min(sizes.len() - 1)]
3120    } else {
3121        0
3122    };
3123
3124    // Draw scope-based Gantt chart - move scopes up slightly
3125    for (scope_index, (scope_name, scope_vars)) in sorted_scopes.iter().enumerate() {
3126        let scope_y = plot_y + 10 + (scope_index as f64 * row_height) as i32; // Moved up by 10px
3127
3128        // Get scope color (different color for each scope)
3129        let scope_colors = [
3130            "#3498db", "#e74c3c", "#2ecc71", "#f39c12", "#9b59b6", "#1abc9c",
3131        ];
3132        let scope_color = scope_colors[scope_index % scope_colors.len()];
3133
3134        // Draw scope background bar (full timeline)
3135        let scope_bg = Rectangle::new()
3136            .set("x", plot_x)
3137            .set("y", scope_y)
3138            .set("width", plot_width)
3139            .set("height", (row_height * 0.8) as i32)
3140            .set("fill", scope_color)
3141            .set("opacity", 0.2)
3142            .set("rx", 5);
3143        document = document.add(scope_bg);
3144
3145        // Scope name label
3146        let scope_label = SvgText::new(format!("Scope: {scope_name}"))
3147            .set("x", plot_x - 15)
3148            .set("y", scope_y + (row_height * 0.4) as i32)
3149            .set("text-anchor", "end")
3150            .set("font-size", 11)
3151            .set("font-weight", "bold")
3152            .set("fill", scope_color);
3153        document = document.add(scope_label);
3154
3155        // Draw variables within this scope
3156        for (var_index, alloc) in scope_vars.iter().enumerate() {
3157            let default_name = "unnamed_variable".to_string();
3158            let var_name = alloc.var_name.as_ref().unwrap_or(&default_name);
3159
3160            // Calculate variable bar position
3161            let start_x = plot_x as i32
3162                + ((alloc.timestamp_alloc as f64 - min_time) / time_range * plot_width as f64)
3163                    as i32;
3164            let var_y = scope_y + 5 + (var_index as f64 * 8.0) as i32; // Stack variables within scope
3165
3166            // Get variable type color
3167            let (_, category) = if let Some(type_name) = &alloc.type_name {
3168                simplify_type_name(type_name)
3169            } else {
3170                (
3171                    "Unknown".to_string(),
3172                    "Runtime/System Allocation".to_string(),
3173                )
3174            };
3175            let var_color = get_category_color(&category);
3176
3177            // Variable bar width based on memory size
3178            let max_size = scope_vars.iter().map(|a| a.size).max().unwrap_or(1024) as f64;
3179            let min_size = scope_vars
3180                .iter()
3181                .map(|a| a.size)
3182                .filter(|&s| s > 0)
3183                .min()
3184                .unwrap_or(1) as f64;
3185            let size_ratio = if max_size > min_size {
3186                ((alloc.size as f64).ln() - min_size.ln()) / (max_size.ln() - min_size.ln())
3187            } else {
3188                0.5
3189            };
3190            let bar_width = ((plot_width as f64 * 0.4 * size_ratio) + 30.0) as i32;
3191
3192            // Draw variable bar
3193            let plot_x_i32 = plot_x as i32;
3194            let plot_width_i32 = plot_width as i32;
3195            let var_bar = Rectangle::new()
3196                .set("x", start_x.max(plot_x_i32))
3197                .set("y", var_y)
3198                .set(
3199                    "width",
3200                    bar_width.min(plot_x_i32 + plot_width_i32 - start_x.max(plot_x_i32)),
3201                )
3202                .set("height", 6)
3203                .set("fill", var_color)
3204                .set("stroke", "#ffffff")
3205                .set("stroke-width", 1)
3206                .set("rx", 2)
3207                .set("opacity", 0.9);
3208            document = document.add(var_bar);
3209
3210            // Variable info label
3211            let var_info = format!("{}: {}", var_name, format_bytes(alloc.size));
3212            let var_label = SvgText::new(if var_info.len() > 25 {
3213                format!("{}...", &var_info[..22])
3214            } else {
3215                var_info
3216            })
3217            .set("x", start_x.max(plot_x_i32) + 5)
3218            .set("y", var_y + 4)
3219            .set("font-size", 7)
3220            .set("font-weight", "500")
3221            .set("fill", "#2c3e50");
3222            document = document.add(var_label);
3223        }
3224    }
3225
3226    // Move legend to top-left corner with proportional scaling
3227    let legend_x = chart_x + 20;
3228    let legend_y = chart_y + 20;
3229    let legend_width = 200; // Scaled proportionally
3230    let legend_height = 120; // Scaled proportionally
3231
3232    // Legend background box - completely transparent with no border
3233    let legend_bg = Rectangle::new()
3234        .set("x", legend_x - 10)
3235        .set("y", legend_y - 15)
3236        .set("width", legend_width)
3237        .set("height", legend_height)
3238        .set("fill", "rgba(255,255,255,0.0)") // Transparent background
3239        .set("stroke", "none") // No border
3240        .set("stroke-width", 0)
3241        .set("rx", 5);
3242    document = document.add(legend_bg);
3243
3244    let legend_title = SvgText::new("Type Categories")
3245        .set("x", legend_x)
3246        .set("y", legend_y)
3247        .set("font-size", 10) // Smaller title
3248        .set("font-weight", "bold")
3249        .set("fill", "#2c3e50");
3250    document = document.add(legend_title);
3251
3252    // Add compact system allocation info
3253    let unknown_count = allocations
3254        .iter()
3255        .filter(|a| {
3256            if let Some(type_name) = &a.type_name {
3257                let (_, category) = simplify_type_name(type_name);
3258                category == "Unknown"
3259            } else {
3260                true // No type_name at all
3261            }
3262        })
3263        .count();
3264
3265    let unknown_legend_y = legend_y + 15;
3266
3267    // System allocation color square - smaller
3268    let unknown_color_square = Rectangle::new()
3269        .set("x", legend_x)
3270        .set("y", unknown_legend_y - 6)
3271        .set("width", 8)
3272        .set("height", 8)
3273        .set("fill", "#95a5a6");
3274    document = document.add(unknown_color_square);
3275
3276    // Compact system allocation label
3277    let unknown_label = if unknown_count > 0 {
3278        format!("System ({unknown_count} allocs)")
3279    } else {
3280        "No System Allocs".to_string()
3281    };
3282
3283    let unknown_text = SvgText::new(unknown_label)
3284        .set("x", legend_x + 12)
3285        .set("y", unknown_legend_y - 1)
3286        .set("font-size", 8)
3287        .set("fill", "#2c3e50");
3288    document = document.add(unknown_text);
3289
3290    for (i, category) in categorized.iter().take(4).enumerate() {
3291        // Reduce to 4 items for compact legend
3292        let legend_item_y = legend_y + 30 + (i as i32) * 15; // Reduce spacing
3293
3294        // Color square - smaller size
3295        let color_square = Rectangle::new()
3296            .set("x", legend_x)
3297            .set("y", legend_item_y - 6)
3298            .set("width", 8)
3299            .set("height", 8)
3300            .set("fill", category.color.as_str());
3301        document = document.add(color_square);
3302
3303        // Category name - more compact
3304        let category_name = if category.name.len() > 15 {
3305            format!("{}...", &category.name[..12])
3306        } else {
3307            category.name.clone()
3308        };
3309
3310        let category_text = SvgText::new(category_name)
3311            .set("x", legend_x + 12)
3312            .set("y", legend_item_y - 1)
3313            .set("font-size", 8) // Smaller font
3314            .set("fill", "#2c3e50");
3315        document = document.add(category_text);
3316    }
3317
3318    // Add note about data limitation for Memory Allocation Timeline
3319    let total_tracked = tracked_vars.len();
3320    if total_tracked > max_vars_to_show {
3321        let note_text = format!("Note: Showing top {max_vars_to_show} variables with highest memory usage and longest lifecycles (out of {total_tracked} total tracked variables)");
3322        let note = SvgText::new(note_text)
3323            .set("x", chart_x + 20)
3324            .set("y", chart_y + chart_height - 10)
3325            .set("font-size", 11)
3326            .set("font-style", "italic")
3327            .set("fill", "#7f8c8d");
3328        document = document.add(note);
3329    }
3330
3331    Ok(document)
3332}
3333
3334/// Add memory heatmap visualization (placeholder for now)
3335pub fn add_memory_heatmap(
3336    document: Document,
3337    _allocations: &[AllocationInfo],
3338) -> TrackingResult<Document> {
3339    // For now, just return the document as-is
3340    // This will be replaced with actual heatmap implementation later
3341    Ok(document)
3342}
3343
3344/// Extract scope name from variable name - use actual program scopes
3345fn extract_scope_name(var_name: &str) -> String {
3346    // Extract meaningful scope information based on actual program structure
3347    if var_name.contains("global") {
3348        "Global Scope".to_string()
3349    } else if var_name.contains("static") {
3350        "Static Scope".to_string()
3351    } else if var_name.contains("boxed") {
3352        "Boxed Allocations".to_string()
3353    } else if var_name.contains("shared") || var_name.contains("arc") || var_name.contains("rc") {
3354        "Shared References".to_string()
3355    } else if var_name.contains("node") {
3356        "Graph Nodes".to_string()
3357    } else if var_name.contains("mutable") {
3358        "Mutable Data".to_string()
3359    } else if var_name.contains("_") {
3360        // Use prefix before first underscore as scope
3361        let prefix = var_name.split('_').next().unwrap_or("Unknown");
3362        format!("{} Scope", prefix.to_ascii_uppercase())
3363    } else {
3364        // Group by variable type/pattern
3365        if var_name.len() > 6 {
3366            format!("{} Scope", &var_name[..6].to_ascii_uppercase())
3367        } else {
3368            "Main Scope".to_string()
3369        }
3370    }
3371}
3372
3373#[cfg(test)]
3374mod tests {
3375    use super::*;
3376    use crate::core::types::{AllocationInfo, TypeMemoryUsage};
3377
3378    // Helper function to create test allocation info
3379    fn create_test_allocation(
3380        size: usize,
3381        type_name: Option<String>,
3382        var_name: Option<String>,
3383    ) -> AllocationInfo {
3384        let mut alloc = AllocationInfo::new(0, size);
3385        alloc.type_name = type_name;
3386        alloc.var_name = var_name;
3387        alloc
3388    }
3389
3390    // Helper function to create test type memory usage
3391    fn create_test_type_usage(
3392        type_name: String,
3393        total_size: usize,
3394        allocation_count: usize,
3395    ) -> TypeMemoryUsage {
3396        TypeMemoryUsage {
3397            type_name,
3398            total_size,
3399            allocation_count,
3400            average_size: if allocation_count > 0 {
3401                total_size as f64 / allocation_count as f64
3402            } else {
3403                0.0
3404            },
3405            peak_size: total_size,
3406            current_size: total_size,
3407            efficiency_score: 1.0,
3408        }
3409    }
3410
3411    #[test]
3412    fn test_calculate_allocation_percentiles_empty() {
3413        let allocations = vec![];
3414        let (median, p95) = calculate_allocation_percentiles(&allocations);
3415        assert_eq!(median, 0);
3416        assert_eq!(p95, 0);
3417    }
3418
3419    #[test]
3420    fn test_calculate_allocation_percentiles_single() {
3421        let allocations = vec![create_test_allocation(100, None, None)];
3422        let (median, p95) = calculate_allocation_percentiles(&allocations);
3423        assert_eq!(median, 100);
3424        assert_eq!(p95, 100);
3425    }
3426
3427    #[test]
3428    fn test_calculate_allocation_percentiles_even_count() {
3429        let allocations = vec![
3430            create_test_allocation(100, None, None),
3431            create_test_allocation(200, None, None),
3432            create_test_allocation(300, None, None),
3433            create_test_allocation(400, None, None),
3434        ];
3435        let (median, p95) = calculate_allocation_percentiles(&allocations);
3436        assert_eq!(median, 250); // (200 + 300) / 2
3437        assert_eq!(p95, 400); // 95th percentile of 4 items is the last item
3438    }
3439
3440    #[test]
3441    fn test_calculate_allocation_percentiles_odd_count() {
3442        let allocations = vec![
3443            create_test_allocation(100, None, None),
3444            create_test_allocation(200, None, None),
3445            create_test_allocation(300, None, None),
3446        ];
3447        let (median, p95) = calculate_allocation_percentiles(&allocations);
3448        assert_eq!(median, 200); // Middle element
3449        assert_eq!(p95, 300); // 95th percentile
3450    }
3451
3452    #[test]
3453    fn test_extract_generic_inner_type() {
3454        assert_eq!(extract_generic_inner_type("Vec<i32>", "Vec"), "i32");
3455        assert_eq!(
3456            extract_generic_inner_type("HashMap<String, i32>", "HashMap"),
3457            "String, i32"
3458        );
3459        assert_eq!(
3460            extract_generic_inner_type("Option<String>", "Option"),
3461            "String"
3462        );
3463        assert_eq!(
3464            extract_generic_inner_type("std::vec::Vec<u64>", "Vec"),
3465            "u64"
3466        );
3467        assert_eq!(extract_generic_inner_type("NoGeneric", "Vec"), "?");
3468        assert_eq!(
3469            extract_generic_inner_type("Vec<std::string::String>", "Vec"),
3470            "String"
3471        );
3472    }
3473
3474    #[test]
3475    fn test_extract_inner_primitive_types_enhanced() {
3476        let result = extract_inner_primitive_types_enhanced("Vec<i32>");
3477        assert!(result.contains(&"i32".to_string()));
3478
3479        let result = extract_inner_primitive_types_enhanced("HashMap<String, u64>");
3480        // String is not a primitive type in this function, only basic types like u64
3481        assert!(result.contains(&"u64".to_string()));
3482
3483        let result = extract_inner_primitive_types_enhanced("Option<bool>");
3484        assert!(result.contains(&"bool".to_string()));
3485
3486        let result = extract_inner_primitive_types_enhanced("NoGeneric");
3487        assert!(result.is_empty());
3488    }
3489
3490    #[test]
3491    fn test_categorize_allocations_empty() {
3492        let allocations = vec![];
3493        let categories = categorize_allocations(&allocations);
3494        assert!(categories.is_empty());
3495    }
3496
3497    #[test]
3498    fn test_categorize_allocations_with_unknown_types() {
3499        let allocations = vec![
3500            create_test_allocation(100, None, Some("var1".to_string())),
3501            create_test_allocation(200, Some("Unknown".to_string()), Some("var2".to_string())),
3502        ];
3503        let categories = categorize_allocations(&allocations);
3504        assert!(categories.is_empty()); // Should skip unknown types
3505    }
3506
3507    #[test]
3508    fn test_categorize_allocations_valid() {
3509        let allocations = vec![
3510            create_test_allocation(100, Some("Vec<i32>".to_string()), Some("vec1".to_string())),
3511            create_test_allocation(200, Some("String".to_string()), Some("str1".to_string())),
3512            create_test_allocation(150, Some("Vec<u64>".to_string()), Some("vec2".to_string())),
3513        ];
3514        let categories = categorize_allocations(&allocations);
3515
3516        assert!(!categories.is_empty());
3517        // Should be sorted by total size (largest first)
3518        assert!(categories[0].total_size >= categories.get(1).map_or(0, |c| c.total_size));
3519    }
3520
3521    #[test]
3522    fn test_enhance_type_information_empty() {
3523        let memory_by_type = vec![];
3524        let allocations = vec![];
3525        let enhanced = enhance_type_information(&memory_by_type, &allocations);
3526        assert!(enhanced.is_empty());
3527    }
3528
3529    #[test]
3530    fn test_enhance_type_information_basic() {
3531        let memory_by_type = vec![
3532            create_test_type_usage("Vec<i32>".to_string(), 1000, 5),
3533            create_test_type_usage("String".to_string(), 500, 3),
3534        ];
3535        let allocations = vec![
3536            create_test_allocation(
3537                200,
3538                Some("Vec<i32>".to_string()),
3539                Some("my_vec".to_string()),
3540            ),
3541            create_test_allocation(
3542                100,
3543                Some("String".to_string()),
3544                Some("my_string".to_string()),
3545            ),
3546        ];
3547
3548        let enhanced = enhance_type_information(&memory_by_type, &allocations);
3549        assert!(!enhanced.is_empty());
3550
3551        // Check that we have enhanced type info
3552        let vec_info = enhanced.iter().find(|e| e.simplified_name.contains("Vec"));
3553        assert!(vec_info.is_some());
3554
3555        let string_info = enhanced
3556            .iter()
3557            .find(|e| e.simplified_name.contains("String"));
3558        assert!(string_info.is_some());
3559    }
3560
3561    #[test]
3562    fn test_categorize_enhanced_allocations_empty() {
3563        let enhanced_types = vec![];
3564        let categories = categorize_enhanced_allocations(&enhanced_types);
3565        assert!(categories.is_empty());
3566    }
3567
3568    #[test]
3569    fn test_categorize_enhanced_allocations_with_unknown() {
3570        let enhanced_types = vec![EnhancedTypeInfo {
3571            simplified_name: "Unknown".to_string(),
3572            category: "Unknown".to_string(),
3573            subcategory: "Unknown".to_string(),
3574            total_size: 100,
3575            allocation_count: 1,
3576            variable_names: vec!["var1".to_string()],
3577        }];
3578        let categories = categorize_enhanced_allocations(&enhanced_types);
3579        assert!(categories.is_empty()); // Should skip unknown types
3580    }
3581
3582    #[test]
3583    fn test_categorize_enhanced_allocations_valid() {
3584        let enhanced_types = vec![
3585            EnhancedTypeInfo {
3586                simplified_name: "Vec".to_string(),
3587                category: "Collections".to_string(),
3588                subcategory: "Vec<T>".to_string(),
3589                total_size: 1000,
3590                allocation_count: 5,
3591                variable_names: vec!["vec1".to_string(), "vec2".to_string()],
3592            },
3593            EnhancedTypeInfo {
3594                simplified_name: "String".to_string(),
3595                category: "Basic Types".to_string(),
3596                subcategory: "Strings".to_string(),
3597                total_size: 500,
3598                allocation_count: 3,
3599                variable_names: vec!["str1".to_string()],
3600            },
3601        ];
3602
3603        let categories = categorize_enhanced_allocations(&enhanced_types);
3604        assert_eq!(categories.len(), 2);
3605
3606        // Should be sorted by total size (largest first)
3607        assert!(categories[0].total_size >= categories[1].total_size);
3608
3609        // Check category names
3610        let category_names: Vec<&String> = categories.iter().map(|c| &c.name).collect();
3611        assert!(category_names.contains(&&"Collections".to_string()));
3612        assert!(category_names.contains(&&"Basic Types".to_string()));
3613    }
3614
3615    #[test]
3616    fn test_extract_scope_name_patterns() {
3617        assert_eq!(extract_scope_name("global_var"), "Global Scope");
3618        assert_eq!(extract_scope_name("static_data"), "Static Scope");
3619        assert_eq!(extract_scope_name("boxed_value"), "Boxed Allocations");
3620        assert_eq!(extract_scope_name("shared_ptr"), "Shared References");
3621        assert_eq!(extract_scope_name("arc_data"), "Shared References");
3622        assert_eq!(extract_scope_name("rc_value"), "Shared References");
3623        assert_eq!(extract_scope_name("node_data"), "Graph Nodes");
3624        assert_eq!(extract_scope_name("mutable_ref"), "Mutable Data");
3625        assert_eq!(extract_scope_name("test_variable"), "TEST Scope");
3626        assert_eq!(extract_scope_name("verylongname"), "VERYLO Scope");
3627        assert_eq!(extract_scope_name("short"), "Main Scope");
3628    }
3629
3630    #[test]
3631    fn test_analyze_type_with_detailed_subcategory() {
3632        let (simplified, category, subcategory) =
3633            analyze_type_with_detailed_subcategory("Vec<i32>");
3634        assert_eq!(simplified, "Vec<i32>"); // Function returns full type name for Vec
3635        assert_eq!(category, "Collections");
3636        assert_eq!(subcategory, "Vec<T>");
3637
3638        let (simplified, category, subcategory) =
3639            analyze_type_with_detailed_subcategory("HashMap<String, u64>");
3640        assert_eq!(simplified, "HashMap<K,V>");
3641        assert_eq!(category, "Collections");
3642        assert_eq!(subcategory, "HashMap<K,V>");
3643
3644        let (simplified, category, subcategory) = analyze_type_with_detailed_subcategory("String");
3645        assert_eq!(simplified, "String");
3646        assert_eq!(category, "Basic Types");
3647        assert_eq!(subcategory, "Strings");
3648
3649        let (simplified, category, subcategory) = analyze_type_with_detailed_subcategory("i32");
3650        assert_eq!(simplified, "i32");
3651        assert_eq!(category, "Basic Types");
3652        assert_eq!(subcategory, "Integers");
3653
3654        let (simplified, category, subcategory) =
3655            analyze_type_with_detailed_subcategory("Box<String>");
3656        assert_eq!(simplified, "String"); // Based on actual function implementation
3657        assert_eq!(category, "Smart Pointers");
3658        assert_eq!(subcategory, "Box<T>");
3659    }
3660
3661    #[test]
3662    fn test_extract_name_and_percentage() {
3663        let (name, percentage) = extract_name_and_percentage("Collections (75.5%)");
3664        assert_eq!(name, "Collections");
3665        assert_eq!(percentage, Some("75.5%"));
3666
3667        let (name, percentage) = extract_name_and_percentage("Basic Types");
3668        assert_eq!(name, "Basic Types");
3669        assert_eq!(percentage, None);
3670
3671        let (name, percentage) = extract_name_and_percentage("Vec<T> (100.0%)");
3672        assert_eq!(name, "Vec<T>");
3673        assert_eq!(percentage, Some("100.0%"));
3674    }
3675
3676    #[test]
3677    fn test_calculate_font_size() {
3678        assert_eq!(calculate_font_size(50.0), 9); // Based on actual implementation
3679        assert_eq!(calculate_font_size(100.0), 11); // Based on actual implementation
3680        assert_eq!(calculate_font_size(200.0), 13); // Based on actual implementation
3681        assert_eq!(calculate_font_size(400.0), 15); // Based on actual implementation
3682        assert_eq!(calculate_font_size(1000.0), 15); // Based on actual implementation
3683    }
3684
3685    #[test]
3686    fn test_build_collections_node_empty() {
3687        let collections_types = vec![];
3688        let total_memory = 1000;
3689        let node = build_collections_node(&collections_types, total_memory);
3690
3691        assert_eq!(node.name, "Collections");
3692        assert_eq!(node.size, 1);
3693        assert_eq!(node.percentage, 0.0);
3694        assert!(node.children.is_empty());
3695    }
3696
3697    #[test]
3698    fn test_build_collections_node_with_data() {
3699        let enhanced_type = EnhancedTypeInfo {
3700            simplified_name: "Vec".to_string(),
3701            category: "Collections".to_string(),
3702            subcategory: "Vec<T>".to_string(),
3703            total_size: 500,
3704            allocation_count: 3,
3705            variable_names: vec!["vec1".to_string()],
3706        };
3707        let collections_types = vec![&enhanced_type];
3708        let total_memory = 1000;
3709        let node = build_collections_node(&collections_types, total_memory);
3710
3711        assert!(node.name.contains("Collections"));
3712        assert_eq!(node.size, 500);
3713        assert_eq!(node.percentage, 50.0);
3714        assert!(!node.children.is_empty());
3715    }
3716
3717    #[test]
3718    fn test_build_basic_types_node_empty() {
3719        let basic_types = vec![];
3720        let total_memory = 1000;
3721        let node = build_basic_types_node(&basic_types, total_memory);
3722
3723        assert_eq!(node.name, "Basic Types");
3724        assert_eq!(node.size, 1);
3725        assert_eq!(node.percentage, 0.0);
3726        assert!(node.children.is_empty());
3727    }
3728
3729    #[test]
3730    fn test_build_basic_types_node_with_data() {
3731        let enhanced_type = EnhancedTypeInfo {
3732            simplified_name: "String".to_string(),
3733            category: "Basic Types".to_string(),
3734            subcategory: "Strings".to_string(),
3735            total_size: 300,
3736            allocation_count: 2,
3737            variable_names: vec!["str1".to_string()],
3738        };
3739        let basic_types = vec![&enhanced_type];
3740        let total_memory = 1000;
3741        let node = build_basic_types_node(&basic_types, total_memory);
3742
3743        assert!(node.name.contains("Basic Types"));
3744        assert_eq!(node.size, 300);
3745        assert_eq!(node.percentage, 30.0);
3746        assert!(!node.children.is_empty());
3747    }
3748
3749    #[test]
3750    fn test_analyze_data_distribution_minimal() {
3751        let collections_types = vec![];
3752        let basic_types = vec![];
3753        let smart_pointers_types = vec![];
3754        let other_types = vec![];
3755        let total_memory = 1000;
3756
3757        let strategy = analyze_data_distribution(
3758            &collections_types,
3759            &basic_types,
3760            &smart_pointers_types,
3761            &other_types,
3762            total_memory,
3763        );
3764
3765        matches!(strategy, TreemapLayoutStrategy::MinimalLayout);
3766    }
3767
3768    #[test]
3769    fn test_analyze_data_distribution_collections_dominant() {
3770        let enhanced_type = EnhancedTypeInfo {
3771            simplified_name: "Vec".to_string(),
3772            category: "Collections".to_string(),
3773            subcategory: "Vec<T>".to_string(),
3774            total_size: 900,
3775            allocation_count: 5,
3776            variable_names: vec!["vec1".to_string()],
3777        };
3778        let collections_types = vec![&enhanced_type];
3779        let basic_types = vec![];
3780        let smart_pointers_types = vec![];
3781        let other_types = vec![];
3782        let total_memory = 1000;
3783
3784        let strategy = analyze_data_distribution(
3785            &collections_types,
3786            &basic_types,
3787            &smart_pointers_types,
3788            &other_types,
3789            total_memory,
3790        );
3791
3792        matches!(strategy, TreemapLayoutStrategy::CollectionsOnlyLayout);
3793    }
3794
3795    #[test]
3796    fn test_treemap_node_creation() {
3797        let node = TreemapNode {
3798            name: "Test Node".to_string(),
3799            size: 100,
3800            percentage: 50.0,
3801            color: "#ff0000".to_string(),
3802            children: vec![],
3803        };
3804
3805        assert_eq!(node.name, "Test Node");
3806        assert_eq!(node.size, 100);
3807        assert_eq!(node.percentage, 50.0);
3808        assert_eq!(node.color, "#ff0000");
3809        assert!(node.children.is_empty());
3810    }
3811
3812    #[test]
3813    fn test_allocation_category_creation() {
3814        let category = AllocationCategory {
3815            name: "Test Category".to_string(),
3816            allocations: vec![],
3817            total_size: 500,
3818            color: "#00ff00".to_string(),
3819        };
3820
3821        assert_eq!(category.name, "Test Category");
3822        assert_eq!(category.total_size, 500);
3823        assert_eq!(category.color, "#00ff00");
3824        assert!(category.allocations.is_empty());
3825    }
3826
3827    #[test]
3828    fn test_enhanced_type_info_creation() {
3829        let enhanced_type = EnhancedTypeInfo {
3830            simplified_name: "TestType".to_string(),
3831            category: "TestCategory".to_string(),
3832            subcategory: "TestSubcategory".to_string(),
3833            total_size: 1000,
3834            allocation_count: 10,
3835            variable_names: vec!["var1".to_string(), "var2".to_string()],
3836        };
3837
3838        assert_eq!(enhanced_type.simplified_name, "TestType");
3839        assert_eq!(enhanced_type.category, "TestCategory");
3840        assert_eq!(enhanced_type.subcategory, "TestSubcategory");
3841        assert_eq!(enhanced_type.total_size, 1000);
3842        assert_eq!(enhanced_type.allocation_count, 10);
3843        assert_eq!(enhanced_type.variable_names.len(), 2);
3844    }
3845
3846    // Test for add_memory_heatmap function
3847    #[test]
3848    fn test_add_memory_heatmap() {
3849        let document = svg::Document::new();
3850        let allocations = vec![];
3851
3852        let result = add_memory_heatmap(document, &allocations);
3853        assert!(result.is_ok());
3854    }
3855}