memscope_rs/export/
visualization.rs

1//! Unified visualization module for memscope-rs
2//! Provides memory analysis and lifecycle timeline SVG exports
3
4use crate::core::tracker::MemoryTracker;
5use crate::core::types::{AllocationInfo, MemoryStats, TrackingError, TrackingResult};
6use crate::utils::{format_bytes, get_simple_type, get_type_color, get_type_gradient_colors};
7use std::collections::HashMap;
8use std::fs::File;
9use std::path::Path;
10use svg::node::element::{
11    Circle, Definitions, Group, Line, Marker, Polygon, Rectangle, Style, Text as SvgText,
12};
13use svg::Document;
14
15use crate::unsafe_ffi_tracker::{
16    AllocationSource, BoundaryEventType, EnhancedAllocationInfo, SafetyViolation, UnsafeFFITracker,
17};
18
19/// Export memory analysis visualization showing variable names, types, and usage
20pub fn export_memory_analysis<P: AsRef<Path>>(
21    tracker: &MemoryTracker,
22    path: P,
23) -> TrackingResult<()> {
24    // CRITICAL FIX: Set export mode to prevent recursive tracking during SVG generation
25    thread_local! {
26        static SVG_EXPORT_MODE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
27    }
28
29    // Check if already in SVG export mode to prevent nested exports
30    let already_exporting = SVG_EXPORT_MODE.with(|mode| mode.get());
31    if already_exporting {
32        return Ok(()); // Skip nested export to prevent recursion
33    }
34
35    // Set SVG export mode
36    SVG_EXPORT_MODE.with(|mode| mode.set(true));
37
38    let path = path.as_ref();
39    tracing::info!("Exporting memory analysis to: {}", path.display());
40
41    if let Some(parent) = path.parent() {
42        if !parent.exists() {
43            std::fs::create_dir_all(parent)?;
44        }
45    }
46
47    // CRITICAL FIX: Get stats and allocations at the same time to ensure data consistency
48    let stats = tracker.get_stats()?;
49    let active_allocations = tracker.get_active_allocations()?;
50
51    // CRITICAL FIX: Use actual active memory instead of potentially corrupted peak_memory
52    let actual_memory_usage = active_allocations.iter().map(|a| a.size).sum::<usize>();
53
54    // Override peak_memory if it's unreasonably high compared to active allocations
55    let corrected_peak_memory = if stats.peak_memory > actual_memory_usage * 2 {
56        // If peak_memory is more than 2x actual usage, it's likely corrupted from recursive tracking
57        // Use the larger of actual usage or active memory as the corrected value
58        actual_memory_usage.max(stats.active_memory)
59    } else {
60        stats.peak_memory
61    };
62
63    tracing::info!(
64        "Memory correction: original peak_memory={}, active_memory={}, actual_usage={}, corrected_peak={}",
65        stats.peak_memory, stats.active_memory, actual_memory_usage, corrected_peak_memory
66    );
67
68    // Debug: Log the corrected peak memory value used in SVG export
69    tracing::info!(
70        "SVG Export - Using corrected peak_memory: {} bytes ({})",
71        corrected_peak_memory,
72        crate::utils::format_bytes(corrected_peak_memory)
73    );
74
75    // Create corrected stats for SVG generation
76    let mut corrected_stats = stats.clone();
77    corrected_stats.peak_memory = corrected_peak_memory;
78
79    let document = create_memory_analysis_svg(&active_allocations, &corrected_stats, tracker)?;
80
81    let mut file = File::create(path)?;
82    svg::write(&mut file, &document)
83        .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
84
85    tracing::info!("Successfully exported memory analysis SVG");
86
87    // CRITICAL FIX: Clear SVG export mode after generation
88    SVG_EXPORT_MODE.with(|mode| mode.set(false));
89
90    Ok(())
91}
92
93/// Export interactive lifecycle timeline showing variable lifecycles and relationships
94pub fn export_lifecycle_timeline<P: AsRef<Path>>(
95    tracker: &MemoryTracker,
96    path: P,
97) -> TrackingResult<()> {
98    let path = path.as_ref();
99    tracing::info!("Exporting lifecycle timeline to: {}", path.display());
100
101    if let Some(parent) = path.parent() {
102        if !parent.exists() {
103            std::fs::create_dir_all(parent)?;
104        }
105    }
106
107    let active_allocations = tracker.get_active_allocations()?;
108    let stats = tracker.get_stats()?;
109
110    let document = create_lifecycle_timeline_svg(&active_allocations, &stats)?;
111
112    let mut file = File::create(path)?;
113    svg::write(&mut file, &document)
114        .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
115
116    tracing::info!("Successfully exported lifecycle timeline SVG");
117    Ok(())
118}
119
120/// Create comprehensive memory analysis SVG with original 12-section layout
121fn create_memory_analysis_svg(
122    allocations: &[AllocationInfo],
123    stats: &MemoryStats,
124    tracker: &MemoryTracker,
125) -> TrackingResult<Document> {
126    // Create comprehensive memory analysis using the original enhanced export logic
127    let width = 1800;
128    let height = 2000; // Reduced height after repositioning modules
129
130    let mut document = Document::new()
131        .set("viewBox", (0, 0, width, height))
132        .set("width", width)
133        .set("height", height)
134        .set("style", "background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 100%); font-family: 'Segoe UI', Arial, sans-serif;");
135
136    // 1. Title: Rust Memory Usage Analysis
137    document = crate::export::export_enhanced::add_enhanced_header(document, stats, allocations)?;
138
139    // 3. Performance Dashboard - REMOVED to prevent overlap with header metrics
140    // document =
141    //     crate::export_enhanced::add_enhanced_timeline_dashboard(document, stats, allocations)?;
142
143    // 4. Memory Allocation Heatmap
144    document = crate::export::export_enhanced::add_memory_heatmap(document, allocations)?;
145
146    // 5. Left side: Memory Usage by Type
147    // Fixed: Get actual memory type data instead of empty array
148    let memory_by_type_data = tracker.get_memory_by_type().unwrap_or_default();
149    let memory_by_type = enhance_type_information(&memory_by_type_data, allocations);
150    document = crate::export::export_enhanced::add_enhanced_type_chart(document, &memory_by_type)?;
151
152    // 6. Right side: Memory Fragmentation Analysis
153    document = crate::export::export_enhanced::add_fragmentation_analysis(document, allocations)?;
154
155    // 7. Left side: Tracked Variables by Category
156    // FIXED: Use same enhanced data source as Memory Usage by Type for consistency
157    let categorized = categorize_enhanced_allocations(&memory_by_type);
158    document = crate::export::export_enhanced::add_categorized_allocations(document, &categorized)?;
159
160    // 8. Right side: Call Stack Analysis
161    document = crate::export::export_enhanced::add_callstack_analysis(document, allocations)?;
162
163    // 9. Memory Growth Trends
164    document =
165        crate::export::export_enhanced::add_memory_growth_trends(document, allocations, stats)?;
166
167    // 10. Variable Allocation Timeline
168    document = crate::export::export_enhanced::add_memory_timeline(document, allocations, stats)?;
169
170    // 11. Bottom left: Interactive Legend & Guide
171    document = crate::export::export_enhanced::add_interactive_legend(document)?;
172
173    // 12. Bottom right: Memory Analysis Summary
174    document =
175        crate::export::export_enhanced::add_comprehensive_summary(document, stats, allocations)?;
176
177    Ok(document)
178}
179
180/// Create lifecycle timeline SVG with interactive features
181fn create_lifecycle_timeline_svg(
182    allocations: &[AllocationInfo],
183    stats: &MemoryStats,
184) -> TrackingResult<Document> {
185    let width = 1600;
186    let height = 1200;
187
188    let mut document = Document::new()
189        .set("viewBox", (0, 0, width, height))
190        .set("width", width)
191        .set("height", height)
192        .set("style", "background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Inter', 'Segoe UI', sans-serif;");
193
194    // Add interactive styles
195    let styles = Style::new(
196        r#"
197        .timeline-bar { transition: all 0.3s ease; cursor: pointer; }
198        .timeline-bar:hover { stroke: #FFFFFF; stroke-width: 3; filter: drop-shadow(0 0 12px rgba(255,255,255,0.8)); }
199        .variable-label { fill: #FFFFFF; font-size: 13px; font-weight: 600; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
200        .memory-label { fill: #E2E8F0; font-size: 11px; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
201        .section-title { fill: #FFFFFF; font-size: 20px; font-weight: 700; text-shadow: 2px 2px 4px rgba(0,0,0,0.5); }
202        .section-bg { fill: rgba(255,255,255,0.1); stroke: rgba(255,255,255,0.2); stroke-width: 1; rx: 12; }
203    "#,
204    );
205    document = document.add(styles);
206
207    // Title - Scope Matrix & Lifecycle Visualization as specified in task.md
208    let title = SvgText::new("Scope Matrix & Lifecycle Visualization")
209        .set("x", width / 2)
210        .set("y", 40)
211        .set("text-anchor", "middle")
212        .set("font-size", 32)
213        .set("font-weight", "bold")
214        .set("fill", "#FFFFFF")
215        .set("style", "text-shadow: 3px 3px 6px rgba(0,0,0,0.5);");
216    document = document.add(title);
217
218    // PROMINENT GLOBAL LEGEND for Progress Bar explanation
219    document = add_prominent_progress_bar_legend(document, width);
220
221    let tracked_vars: Vec<_> = allocations
222        .iter()
223        .filter(|a| a.var_name.is_some())
224        .collect();
225
226    // tracing::info!(
227    //     "Found {} total allocations, {} with variable names",
228    //     allocations.len(),
229    //     tracked_vars.len()
230    // );
231
232    // Debug: Print the tracked variables we found
233    // for (i, var) in tracked_vars.iter().enumerate() {
234    //     tracing::info!(
235    //         "Tracked var {}: {} ({})",
236    //         i + 1,
237    //         var.var_name.as_ref().unwrap_or(&"None".to_string()),
238    //         var.type_name.as_ref().unwrap_or(&"Unknown".to_string())
239    //     );
240    // }
241
242    if tracked_vars.is_empty() {
243        let no_data = SvgText::new(format!(
244            "No tracked variables found (checked {} allocations)",
245            allocations.len()
246        ))
247        .set("x", width / 2)
248        .set("y", height / 2)
249        .set("text-anchor", "middle")
250        .set("font-size", 18)
251        .set("fill", "#FFFFFF");
252        document = document.add(no_data);
253        return Ok(document);
254    }
255
256    // Add matrix layout instead of timeline - ADJUSTED Y position for global legend
257    document = add_matrix_layout_section(document, &tracked_vars, 50, 130)?;
258
259    // Add memory analysis section - aligned and consistent width
260    document = add_memory_section(document, &tracked_vars, stats, 550, width - 100)?;
261
262    // Add variable relationships section - aligned and consistent width
263    document = add_relationships_section(document, &tracked_vars, 900, width - 100)?;
264
265    Ok(document)
266}
267
268/// Add memory section for lifecycle visualization
269fn add_memory_section(
270    mut document: Document,
271    tracked_vars: &[&AllocationInfo],
272    _stats: &MemoryStats,
273    start_y: i32,
274    section_height: i32,
275) -> TrackingResult<Document> {
276    // Section background
277    let section_bg = Rectangle::new()
278        .set("x", 50)
279        .set("y", start_y - 20)
280        .set("width", 1500)
281        .set("height", section_height)
282        .set("class", "section-bg");
283    document = document.add(section_bg);
284
285    // Section title - TOP 3 MEMORY ANALYSIS as specified in task.md
286    let section_title = SvgText::new("Top 3 Memory Analysis")
287        .set("x", 70)
288        .set("y", start_y + 10)
289        .set("class", "section-title");
290    document = document.add(section_title);
291
292    // Group by type and get TOP 3 ONLY
293    let mut type_stats: HashMap<String, (usize, usize, Vec<String>)> = HashMap::new();
294    for allocation in tracked_vars {
295        if let Some(var_name) = &allocation.var_name {
296            let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
297            let simple_type = get_simple_type(type_name);
298
299            let entry = type_stats.entry(simple_type).or_insert((0, 0, Vec::new()));
300            entry.0 += 1;
301            entry.1 += allocation.size;
302            entry
303                .2
304                .push(format!("{}({})", var_name, format_bytes(allocation.size)));
305        }
306    }
307
308    // Sort by total size and take TOP 3 ONLY
309    let mut sorted_types: Vec<_> = type_stats.into_iter().collect();
310    sorted_types.sort_by(|a, b| b.1 .1.cmp(&a.1 .1)); // Sort by total size descending
311    sorted_types.truncate(3); // STRICTLY TOP 3
312
313    // Draw memory bars - TOP 3 ONLY
314    let chart_x = 100;
315    let chart_y = start_y + 50;
316    let max_size = sorted_types
317        .iter()
318        .map(|(_, (_, size, _))| *size)
319        .max()
320        .unwrap_or(1);
321
322    for (i, (type_name, (count, total_size, vars))) in sorted_types.iter().enumerate() {
323        let y = chart_y + (i as i32) * 40;
324        let bar_width = ((*total_size as f64 / max_size as f64) * 400.0) as i32;
325
326        let color = get_type_color(type_name);
327        let memory_bar = Rectangle::new()
328            .set("x", chart_x)
329            .set("y", y)
330            .set("width", bar_width)
331            .set("height", 25)
332            .set("fill", color)
333            .set("rx", 4);
334        document = document.add(memory_bar);
335
336        // Type label inside the bar
337        let type_label = SvgText::new(format!("{type_name} ({count} vars)"))
338            .set("x", chart_x + 10)
339            .set("y", y + 17)
340            .set("font-size", 12)
341            .set("font-weight", "bold")
342            .set("fill", "#FFFFFF")
343            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
344        document = document.add(type_label);
345
346        // Enhanced variable names display - more prominent
347        let vars_text = vars.join(" | ");
348        let vars_label = SvgText::new(if vars_text.len() > 80 {
349            format!("{}...", &vars_text[..77])
350        } else {
351            vars_text
352        })
353        .set("x", chart_x + bar_width + 15)
354        .set("y", y + 17)
355        .set("font-size", 11)
356        .set("font-weight", "600")
357        .set("fill", "#FFFFFF")
358        .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.6)");
359        document = document.add(vars_label);
360    }
361
362    // Placeholder for lifecycle timeline
363    Ok(document)
364}
365
366/// Add relationships section for lifecycle visualization - ENHANCED VARIABLE RELATIONSHIPS
367fn add_relationships_section(
368    mut document: Document,
369    tracked_vars: &[&AllocationInfo],
370    start_y: i32,
371    section_height: i32,
372) -> TrackingResult<Document> {
373    // Section background
374    let section_bg = Rectangle::new()
375        .set("x", 50)
376        .set("y", start_y - 20)
377        .set("width", 1500)
378        .set("height", section_height)
379        .set("class", "section-bg");
380    document = document.add(section_bg);
381
382    // Section title
383    let section_title = SvgText::new("Variable Relationships - Ownership & Borrowing")
384        .set("x", 70)
385        .set("y", start_y + 10)
386        .set("class", "section-title");
387    document = document.add(section_title);
388
389    // Group variables by scope for better organization
390    let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
391    for var in tracked_vars {
392        let scope = identify_precise_scope(var);
393        scope_groups.entry(scope).or_default().push(*var);
394    }
395
396    // Draw scope group backgrounds first
397    let mut scope_positions = HashMap::new();
398    let start_x = 100;
399    let group_spacing_x = 400;
400    let group_spacing_y = 200;
401
402    for (i, (scope_name, _vars)) in scope_groups.iter().enumerate() {
403        let group_x = start_x + (i % 3) as i32 * group_spacing_x;
404        let group_y = start_y + 50 + (i / 3) as i32 * group_spacing_y;
405
406        // Scope group background with subtle color
407        let group_bg = Rectangle::new()
408            .set("x", group_x - 20)
409            .set("y", group_y - 20)
410            .set("width", 300)
411            .set("height", 150)
412            .set("fill", get_scope_background_color(scope_name))
413            .set("stroke", get_scope_border_color(scope_name))
414            .set("stroke-width", 2)
415            .set(
416                "stroke-dasharray",
417                if scope_name == "Global" {
418                    "none"
419                } else {
420                    "5,3"
421                },
422            )
423            .set("rx", 8)
424            .set("opacity", "0.3");
425        document = document.add(group_bg);
426
427        // Scope label
428        let scope_label = SvgText::new(format!("Scope: {scope_name}"))
429            .set("x", group_x - 10)
430            .set("y", group_y - 5)
431            .set("font-size", 12)
432            .set("font-weight", "bold")
433            .set("fill", "#FFFFFF");
434        document = document.add(scope_label);
435
436        scope_positions.insert(scope_name, (group_x, group_y));
437    }
438
439    // Note: Relationship analysis removed to eliminate unused code
440
441    // Draw variable nodes AFTER lines (on top)
442    for (scope_name, vars) in &scope_groups {
443        if let Some((group_x, group_y)) = scope_positions.get(scope_name) {
444            for (i, allocation) in vars.iter().take(4).enumerate() {
445                // Limit to 4 per scope
446                let node_x = group_x + (i % 2) as i32 * 120 + 40;
447                let node_y = group_y + (i / 2) as i32 * 60 + 30;
448
449                document = draw_variable_node(document, allocation, node_x, node_y)?;
450            }
451        }
452    }
453
454    // Add relationship legend
455    document = add_relationship_legend(document, start_y + section_height - 100)?;
456
457    Ok(document)
458}
459
460/// Draw variable node with enhanced styling
461fn draw_variable_node(
462    mut document: Document,
463    allocation: &AllocationInfo,
464    x: i32,
465    y: i32,
466) -> TrackingResult<Document> {
467    let default_name = "<unknown>".to_string();
468    let var_name = allocation.var_name.as_ref().unwrap_or(&default_name);
469    let type_name = allocation.type_name.as_deref().unwrap_or("Unknown");
470    let simple_type = get_simple_type(type_name);
471    let color = get_type_color(&simple_type);
472
473    // Variable node with tooltip support
474    let node = Circle::new()
475        .set("cx", x)
476        .set("cy", y)
477        .set("r", 20)
478        .set("fill", color)
479        .set("stroke", "#FFFFFF")
480        .set("stroke-width", 2)
481        .set(
482            "title",
483            format!(
484                "{}: {} ({})",
485                var_name,
486                simple_type,
487                format_bytes(allocation.size)
488            ),
489        );
490    document = document.add(node);
491
492    // Variable name (truncated if too long)
493    let display_name = if var_name.len() > 8 {
494        format!("{}...", &var_name[..6])
495    } else {
496        var_name.clone()
497    };
498
499    let name_label = SvgText::new(display_name)
500        .set("x", x)
501        .set("y", y + 3)
502        .set("text-anchor", "middle")
503        .set("font-size", 9)
504        .set("font-weight", "bold")
505        .set("fill", "#FFFFFF");
506    document = document.add(name_label);
507
508    // Type and size below
509    let info_text = format!("{} | {}", simple_type, format_bytes(allocation.size));
510    let info_label = SvgText::new(info_text)
511        .set("x", x)
512        .set("y", y + 35)
513        .set("text-anchor", "middle")
514        .set("font-size", 7)
515        .set("fill", "#E2E8F0");
516    document = document.add(info_label);
517
518    Ok(document)
519}
520
521/// Add relationship legend
522fn add_relationship_legend(mut document: Document, start_y: i32) -> TrackingResult<Document> {
523    let legend_items = [
524        ("Ownership Transfer", "#E74C3C", "solid", "4"),
525        ("Mutable Borrow", "#3498DB", "solid", "3"),
526        ("Immutable Borrow", "#27AE60", "solid", "2"),
527        ("Clone", "#95A5A6", "solid", "2"),
528        ("Shared Pointer", "#9B59B6", "8,4", "3"),
529        ("Indirect Reference", "#F39C12", "4,2", "1"),
530    ];
531
532    for (i, (label, color, dash, width)) in legend_items.iter().enumerate() {
533        let x = 100 + (i % 3) as i32 * 200;
534        let y = start_y + (i / 3) as i32 * 25;
535
536        // Legend line
537        let legend_line = Line::new()
538            .set("x1", x)
539            .set("y1", y)
540            .set("x2", x + 30)
541            .set("y2", y)
542            .set("stroke", *color)
543            .set("stroke-width", *width)
544            .set("stroke-dasharray", *dash);
545        document = document.add(legend_line);
546
547        // Legend label
548        let legend_label = SvgText::new(*label)
549            .set("x", x + 35)
550            .set("y", y + 4)
551            .set("font-size", 10)
552            .set("fill", "#FFFFFF");
553        document = document.add(legend_label);
554    }
555
556    Ok(document)
557}
558
559/// Get scope background color
560fn get_scope_background_color(scope_name: &str) -> &'static str {
561    match scope_name {
562        "Global" => "rgba(52, 73, 94, 0.2)",
563        _ => "rgba(52, 152, 219, 0.2)",
564    }
565}
566
567/// Get scope border color
568fn get_scope_border_color(scope_name: &str) -> &'static str {
569    match scope_name {
570        "Global" => "#34495E",
571        _ => "#3498DB",
572    }
573}
574
575/// Calculate matrix size based on 5-variable standard with dynamic shrinking
576fn calculate_dynamic_matrix_size(var_count: usize) -> (i32, i32) {
577    let standard_width = 350; // Standard size for 5 variables
578    let standard_height = 280; // Standard size for 5 variables
579    let card_height = 40; // Height per variable card
580    let header_height = 80; // Header and footer space
581    let standard_vars = 5;
582
583    if var_count <= standard_vars {
584        // SHRINK: Reduce size for fewer variables
585        let actual_content_height = header_height + (var_count as i32 * card_height) + 40; // Bottom space
586        let width_reduction = ((standard_vars - var_count) * 15) as i32; // Gentle width reduction
587        let actual_width = standard_width - width_reduction;
588
589        (actual_width.max(250), actual_content_height.max(150)) // Minimum size protection
590    } else {
591        // FIXED STANDARD SIZE: Always use standard size, show only 5 + "more" indicator
592        (standard_width, standard_height)
593    }
594}
595
596/// Calculate scope lifetime with FIXED Global scope logic using program runtime
597fn calculate_scope_lifetime(scope_name: &str, vars: &[&AllocationInfo]) -> u64 {
598    if vars.is_empty() {
599        return 0;
600    }
601
602    if scope_name == "Global" {
603        // FIXED: Global scope uses total program runtime, not just variable span
604        estimate_program_runtime()
605    } else {
606        // Local scope: calculate based on variable lifetimes
607        let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
608        let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
609        let span = end - start;
610        if span == 0 {
611            // If variables allocated at same time, estimate reasonable duration
612            100 // 100ms default for local scopes
613        } else {
614            span
615        }
616    }
617}
618
619/// Estimate total program runtime for Global scope lifetime calculation
620fn estimate_program_runtime() -> u64 {
621    // Method A: Use a reasonable estimate for program execution time
622    // For memory tracking programs, typically run for at least a few seconds
623    2000 // 2 seconds - reasonable for Global variable lifetime
624}
625
626/// Add prominent global legend for Progress Bar explanation
627fn add_prominent_progress_bar_legend(mut document: Document, svg_width: i32) -> Document {
628    // Prominent background for the legend
629    let legend_bg = Rectangle::new()
630        .set("x", 50)
631        .set("y", 60)
632        .set("width", svg_width - 100)
633        .set("height", 35)
634        .set("fill", "rgba(252, 211, 77, 0.15)")
635        .set("stroke", "#FCD34D")
636        .set("stroke-width", 2)
637        .set("rx", 8)
638        .set("ry", 8);
639    document = document.add(legend_bg);
640
641    // Progress bar icon/example
642    let example_bg = Rectangle::new()
643        .set("x", 70)
644        .set("y", 70)
645        .set("width", 60)
646        .set("height", 8)
647        .set("fill", "rgba(255, 255, 255, 0.2)")
648        .set("rx", 4);
649    document = document.add(example_bg);
650
651    let example_fill = Rectangle::new()
652        .set("x", 70)
653        .set("y", 70)
654        .set("width", 40)
655        .set("height", 8)
656        .set("fill", "#4CAF50")
657        .set("rx", 4);
658    document = document.add(example_fill);
659
660    // Prominent explanation text
661    let legend_text =
662        SvgText::new("📊 Progress Bars show: Variable Size / Largest Variable in Same Scope")
663            .set("x", 150)
664            .set("y", 78)
665            .set("font-size", 14)
666            .set("font-weight", "bold")
667            .set("fill", "#FCD34D")
668            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
669    document = document.add(legend_text);
670
671    // Size example
672    let size_example = SvgText::new("Example: 2.4KB / 5.6KB")
673        .set("x", 150)
674        .set("y", 88)
675        .set("font-size", 10)
676        .set("fill", "#E2E8F0")
677        .set("font-style", "italic");
678    document = document.add(size_example);
679
680    document
681}
682
683/// Prioritize scopes for display based on importance
684fn prioritize_scopes_for_display<'a>(
685    scope_groups: &'a HashMap<String, Vec<&'a AllocationInfo>>,
686) -> Vec<(String, Vec<&'a AllocationInfo>)> {
687    let mut scopes_with_priority: Vec<_> = scope_groups
688        .iter()
689        .map(|(name, vars)| {
690            let priority = calculate_scope_priority(name, vars);
691            let total_memory: usize = vars.iter().map(|v| v.size).sum();
692            (name, vars, priority, total_memory)
693        })
694        .collect();
695
696    // Sort by priority (higher first), then by memory usage (larger first)
697    scopes_with_priority.sort_by(|a, b| b.2.cmp(&a.2).then(b.3.cmp(&a.3)));
698
699    scopes_with_priority
700        .into_iter()
701        .map(|(name, vars, _, _)| (name.clone(), vars.clone()))
702        .collect()
703}
704
705/// Calculate scope priority based on name patterns and characteristics
706fn calculate_scope_priority(scope_name: &str, vars: &[&AllocationInfo]) -> u8 {
707    let name_lower = scope_name.to_lowercase();
708
709    // CRITICAL SCOPES (Priority: 100)
710    if name_lower == "global"
711        || name_lower == "main"
712        || name_lower.contains("error")
713        || name_lower.contains("panic")
714    {
715        return 100;
716    }
717
718    // HIGH PRIORITY (Priority: 80)
719    if name_lower.contains("process")
720        || name_lower.contains("parse")
721        || name_lower.contains("compute")
722        || name_lower.contains("algorithm")
723        || name_lower.contains("core")
724        || name_lower.contains("engine")
725    {
726        return 80;
727    }
728
729    // MEDIUM PRIORITY (Priority: 60)
730    if name_lower.contains("util")
731        || name_lower.contains("helper")
732        || name_lower.contains("format")
733        || name_lower.contains("convert")
734    {
735        return 60;
736    }
737
738    // LOW PRIORITY (Priority: 40)
739    if name_lower.contains("test")
740        || name_lower.contains("debug")
741        || name_lower.contains("macro")
742        || name_lower.contains("generated")
743    {
744        return 40;
745    }
746
747    // DEFAULT PRIORITY based on memory usage and variable count
748    let total_memory: usize = vars.iter().map(|v| v.size).sum();
749    let var_count = vars.len();
750
751    if total_memory > 1024 || var_count > 3 {
752        70 // High memory/variable count
753    } else if total_memory > 256 || var_count > 1 {
754        50 // Medium memory/variable count
755    } else {
756        30 // Low memory/variable count
757    }
758}
759
760/// Export complete scope analysis to JSON file
761fn export_scope_analysis_json(
762    all_scopes: &HashMap<String, Vec<&AllocationInfo>>,
763    displayed_scopes: &[(String, Vec<&AllocationInfo>)],
764) -> TrackingResult<()> {
765    use serde_json::{Map, Value};
766
767    let mut analysis = Map::new();
768
769    // Project analysis summary
770    let mut project_analysis = Map::new();
771    project_analysis.insert(
772        "total_scopes".to_string(),
773        Value::Number((all_scopes.len() as u64).into()),
774    );
775    project_analysis.insert(
776        "displayed_in_svg".to_string(),
777        Value::Number((displayed_scopes.len() as u64).into()),
778    );
779    project_analysis.insert(
780        "exported_to_json".to_string(),
781        Value::Number(((all_scopes.len() - displayed_scopes.len()) as u64).into()),
782    );
783    project_analysis.insert(
784        "layout_strategy".to_string(),
785        Value::String("hierarchical_priority".to_string()),
786    );
787    project_analysis.insert(
788        "generation_timestamp".to_string(),
789        Value::String(
790            std::time::SystemTime::now()
791                .duration_since(std::time::UNIX_EPOCH)
792                .unwrap_or_default()
793                .as_secs()
794                .to_string(),
795        ),
796    );
797    analysis.insert(
798        "project_analysis".to_string(),
799        Value::Object(project_analysis),
800    );
801
802    // All scopes data
803    let mut all_scopes_data = Vec::new();
804    for (scope_name, vars) in all_scopes {
805        let total_memory: usize = vars.iter().map(|v| v.size).sum();
806        let is_displayed = displayed_scopes.iter().any(|(name, _)| name == scope_name);
807
808        let mut scope_data = Map::new();
809        scope_data.insert("scope_name".to_string(), Value::String(scope_name.clone()));
810        scope_data.insert(
811            "total_memory".to_string(),
812            Value::Number((total_memory as u64).into()),
813        );
814        scope_data.insert(
815            "variable_count".to_string(),
816            Value::Number((vars.len() as u64).into()),
817        );
818        scope_data.insert(
819            "display_status".to_string(),
820            Value::String(if is_displayed {
821                "shown_in_svg".to_string()
822            } else {
823                "json_only".to_string()
824            }),
825        );
826        scope_data.insert(
827            "priority".to_string(),
828            Value::Number((calculate_scope_priority(scope_name, vars) as u64).into()),
829        );
830
831        // Variables in this scope
832        let mut variables = Vec::new();
833        for var in vars {
834            if let Some(var_name) = &var.var_name {
835                let mut var_data = Map::new();
836                var_data.insert("name".to_string(), Value::String(var_name.clone()));
837                var_data.insert(
838                    "type".to_string(),
839                    Value::String(var.type_name.as_deref().unwrap_or("Unknown").to_string()),
840                );
841                var_data.insert(
842                    "size_bytes".to_string(),
843                    Value::Number((var.size as u64).into()),
844                );
845                var_data.insert(
846                    "timestamp".to_string(),
847                    Value::Number(var.timestamp_alloc.into()),
848                );
849                variables.push(Value::Object(var_data));
850            }
851        }
852        scope_data.insert("variables".to_string(), Value::Array(variables));
853
854        all_scopes_data.push(Value::Object(scope_data));
855    }
856    analysis.insert("all_scopes".to_string(), Value::Array(all_scopes_data));
857
858    // Write to JSON file
859    let json_content = serde_json::to_string(&Value::Object(analysis)).map_err(|e| {
860        TrackingError::SerializationError(format!("JSON serialization failed: {e}"))
861    })?;
862
863    std::fs::write("scope_analysis.json", json_content)
864        .map_err(|e| TrackingError::IoError(e.to_string()))?;
865
866    tracing::info!("Exported complete scope analysis to scope_analysis.json");
867    Ok(())
868}
869
870/// Get simple type name
871/// Get color based on duration ratio (0.0 to 1.0)
872/// Assign colors based on relative lifecycle length: longest time=dark color, shortest time=white, global scope=special deep blue
873fn get_duration_color(ratio: f64, is_global: bool) -> String {
874    if is_global {
875        // Global scope uses special deep blue color
876        return "#0A2540".to_string();
877    }
878
879    // Create gradient from white to deep blue
880    // ratio = 0.0 (shortest time) -> close to white
881    // ratio = 1.0 (longest time) -> deep blue
882
883    if ratio <= 0.01 {
884        // Very short time or no time difference -> light gray-white
885        "#F8FAFC".to_string()
886    } else {
887        // Calculate RGB values, gradient from light blue-white to deep blue
888        let base_r = 248; // Starting red value (close to white)
889        let base_g = 250; // Starting green value
890        let base_b = 252; // Starting blue value
891
892        let target_r = 30; // Target red value (deep blue)
893        let target_g = 64; // Target green value
894        let target_b = 175; // Target blue value
895
896        // Use smooth gradient function
897        let smooth_ratio = ratio.powf(0.7); // Make gradient smoother
898
899        let r = (base_r as f64 + (target_r as f64 - base_r as f64) * smooth_ratio) as u8;
900        let g = (base_g as f64 + (target_g as f64 - base_g as f64) * smooth_ratio) as u8;
901        let b = (base_b as f64 + (target_b as f64 - base_b as f64) * smooth_ratio) as u8;
902
903        format!("#{r:02X}{g:02X}{b:02X}")
904    }
905}
906
907/// Add matrix layout section with INTELLIGENT 15-SCOPE LIMITATION
908fn add_matrix_layout_section(
909    mut document: Document,
910    tracked_vars: &[&AllocationInfo],
911    start_x: i32,
912    start_y: i32,
913) -> TrackingResult<Document> {
914    // Group variables by scope
915    let mut scope_groups: HashMap<String, Vec<&AllocationInfo>> = HashMap::new();
916    for var in tracked_vars {
917        let scope = identify_precise_scope(var);
918        scope_groups.entry(scope).or_default().push(*var);
919    }
920
921    // INTELLIGENT SCOPE PRIORITIZATION - Maximum 15 scopes
922    let prioritized_scopes = prioritize_scopes_for_display(&scope_groups);
923    let selected_scopes: Vec<_> = prioritized_scopes.into_iter().take(15).collect();
924
925    // tracing::info!(
926    //     "Total scopes found: {}, displaying: {}",
927    //     scope_groups.len(),
928    //     selected_scopes.len()
929    // );
930
931    // Calculate maximum duration across all SELECTED scopes for relative color scaling
932    let max_duration = selected_scopes
933        .iter()
934        .map(|(_, vars)| {
935            if !vars.is_empty() {
936                let start = vars.iter().map(|v| v.timestamp_alloc).min().unwrap_or(0);
937                let end = vars.iter().map(|v| v.timestamp_alloc).max().unwrap_or(0);
938                end - start
939            } else {
940                0
941            }
942        })
943        .max()
944        .unwrap_or(1); // Avoid division by zero
945
946    // DYNAMIC GRID LAYOUT - 3 columns, up to 5 rows
947    let base_matrix_width = 350;
948    let base_matrix_height = 180;
949    let spacing_x = 450; // Increased spacing to prevent matrix overlap
950    let spacing_y = 250;
951
952    let mut positions = Vec::new();
953
954    // Calculate positions for SELECTED matrices only
955    for (i, (scope_name, _)) in selected_scopes.iter().enumerate() {
956        let col = i % 3;
957        let row = i / 3;
958        let x = start_x + (col as i32 * spacing_x);
959        let y = start_y + (row as i32 * spacing_y);
960        positions.push((scope_name, x, y));
961    }
962
963    // Draw relationship lines first (only for displayed scopes)
964    for (i, (scope_name, x, y)) in positions.iter().enumerate() {
965        if *scope_name != "Global" && i > 0 {
966            // Find Global scope position
967            if let Some((_, global_x, global_y)) =
968                positions.iter().find(|(name, _, _)| *name == "Global")
969            {
970                let line = Line::new()
971                    .set("x1", global_x + base_matrix_width / 2)
972                    .set("y1", global_y + base_matrix_height)
973                    .set("x2", x + base_matrix_width / 2)
974                    .set("y2", *y)
975                    .set("stroke", "#7F8C8D")
976                    .set("stroke-width", 2)
977                    .set("stroke-dasharray", "5,3");
978                document = document.add(line);
979            }
980        }
981    }
982
983    // Render SELECTED scope matrices with relative color scaling
984    for ((scope_name, vars), (_, x, y)) in selected_scopes.iter().zip(positions.iter()) {
985        document = render_scope_matrix_fixed(
986            document,
987            scope_name,
988            vars,
989            *x,
990            *y,
991            base_matrix_width,
992            base_matrix_height,
993            max_duration,
994        )?;
995    }
996
997    // Export complete data to JSON if there are overflow scopes
998    if scope_groups.len() > 15 {
999        export_scope_analysis_json(&scope_groups, &selected_scopes)?;
1000    }
1001
1002    Ok(document)
1003}
1004
1005/// Render single scope matrix with DYNAMIC SIZING and ENHANCED MEMORY VISUALIZATION
1006#[allow(clippy::too_many_arguments)]
1007fn render_scope_matrix_fixed(
1008    mut document: Document,
1009    scope_name: &str,
1010    vars: &[&AllocationInfo],
1011    x: i32,
1012    y: i32,
1013    width: i32,
1014    height: i32,
1015    max_duration: u64, // Maximum duration across all scopes for normalization
1016) -> TrackingResult<Document> {
1017    // DYNAMIC MATRIX SIZING based on variable count
1018    let (dynamic_width, dynamic_height) = calculate_dynamic_matrix_size(vars.len());
1019    let actual_width = dynamic_width.max(width);
1020    let actual_height = dynamic_height.max(height);
1021
1022    let mut matrix_group = Group::new().set("transform", format!("translate({x}, {y})"));
1023
1024    // ENHANCED SCOPE LIFETIME CALCULATION
1025    let duration = calculate_scope_lifetime(scope_name, vars);
1026    let total_memory = vars.iter().map(|v| v.size).sum::<usize>();
1027    // FIXED: Remove incorrect peak_memory calculation - this was calculating max single allocation
1028    // instead of peak total memory. Peak memory should come from stats.peak_memory which is
1029    // the historical maximum of total active memory, not the maximum single allocation size.
1030    let active_vars = vars.len();
1031
1032    // Calculate duration ratio (0.0 to 1.0)
1033    let duration_ratio = if max_duration > 0 {
1034        duration as f64 / max_duration as f64
1035    } else {
1036        0.0
1037    };
1038
1039    // Get border color based on duration ratio and scope type
1040    let is_global = scope_name == "Global";
1041    let border_color = get_duration_color(duration_ratio, is_global);
1042
1043    // ENHANCED MATRIX CONTAINER with dynamic sizing
1044    let container = Rectangle::new()
1045        .set("width", actual_width)
1046        .set("height", actual_height)
1047        .set("fill", "rgba(30, 64, 175, 0.1)")
1048        .set("stroke", border_color.as_str())
1049        .set("stroke-width", 3)
1050        .set(
1051            "stroke-dasharray",
1052            if scope_name != "Global" {
1053                "8,4"
1054            } else {
1055                "none"
1056            },
1057        )
1058        .set("rx", 12);
1059    matrix_group = matrix_group.add(container);
1060
1061    // ENHANCED SCOPE HEADER with comprehensive memory overview - ENGLISH ONLY
1062    let header_text = format!(
1063        "Scope: {} | Memory: {} | Variables: {} | Lifetime: {}ms",
1064        scope_name,
1065        format_bytes(total_memory),
1066        active_vars,
1067        duration
1068    );
1069    let enhanced_title = SvgText::new(header_text)
1070        .set("x", 15)
1071        .set("y", 25)
1072        .set("font-size", 11)
1073        .set("font-weight", "700")
1074        .set("fill", "#f8fafc");
1075    matrix_group = matrix_group.add(enhanced_title);
1076
1077    // Variables section with ENHANCED MODERN CARD DESIGN
1078    let var_start_y = 45;
1079    let card_height = 45; // Increased height for vertical layout (3 lines)
1080    let var_spacing = 50; // More spacing for taller cards
1081    let _font_size = 10;
1082
1083    for (i, var) in vars.iter().take(4).enumerate() {
1084        // Limit to 4 for better layout
1085        let var_y = var_start_y + (i as i32 * var_spacing);
1086        let default_name = "<unknown>".to_string();
1087        let var_name = var.var_name.as_ref().unwrap_or(&default_name);
1088        let type_name = get_simple_type(var.type_name.as_ref().unwrap_or(&"Unknown".to_string()));
1089        let duration_ms = estimate_variable_duration(var);
1090
1091        // Calculate progress percentage for the progress bar
1092        let max_size_in_scope = vars.iter().map(|v| v.size).max().unwrap_or(1);
1093        let progress_ratio = var.size as f64 / max_size_in_scope as f64;
1094        let _progress_width = (progress_ratio * 180.0) as i32;
1095
1096        // ENHANCED MODERN CARD with dynamic width
1097        let card_width = actual_width - 20;
1098        let card_bg = Rectangle::new()
1099            .set("x", 10)
1100            .set("y", var_y - 5)
1101            .set("width", card_width)
1102            .set("height", card_height)
1103            .set("fill", "rgba(255, 255, 255, 0.08)")
1104            .set("stroke", "rgba(255, 255, 255, 0.15)")
1105            .set("stroke-width", 1)
1106            .set("rx", 8)
1107            .set("ry", 8);
1108        matrix_group = matrix_group.add(card_bg);
1109
1110        // Variable name with enhanced styling
1111        let var_label = SvgText::new(var_name)
1112            .set("x", 18)
1113            .set("y", var_y + 8)
1114            .set("font-size", 12)
1115            .set("font-weight", "bold")
1116            .set("fill", "#FFFFFF")
1117            .set("text-shadow", "1px 1px 2px rgba(0,0,0,0.8)");
1118        matrix_group = matrix_group.add(var_label);
1119
1120        // Type label with enhanced color coding
1121        let (type_start_color, _) = get_type_gradient_colors(&type_name);
1122        let type_label = SvgText::new(format!("({type_name})"))
1123            .set("x", 18)
1124            .set("y", var_y + 22)
1125            .set("font-size", 9)
1126            .set("fill", type_start_color)
1127            .set("font-weight", "600");
1128        matrix_group = matrix_group.add(type_label);
1129
1130        // DYNAMIC PROGRESS BAR - Responsive to matrix width
1131        let available_width = card_width - 40; // Leave margins
1132        let progress_bar_width = (available_width as f64 * 0.5) as i32; // 50% of available width
1133        let progress_x = 20; // Fixed left margin
1134
1135        let progress_bg = Rectangle::new()
1136            .set("x", progress_x)
1137            .set("y", var_y + 15) // Moved down to avoid overlap
1138            .set("width", progress_bar_width)
1139            .set("height", 8)
1140            .set("fill", "rgba(255, 255, 255, 0.1)")
1141            .set("stroke", "rgba(255, 255, 255, 0.2)")
1142            .set("stroke-width", 1)
1143            .set("rx", 4)
1144            .set("ry", 4);
1145        matrix_group = matrix_group.add(progress_bg);
1146
1147        // ENHANCED GRADIENT PROGRESS BAR with type-specific colors
1148        let (start_color, _) = get_type_gradient_colors(&type_name);
1149        let progress_fill_width = (progress_ratio * progress_bar_width as f64) as i32;
1150        let progress_fill = Rectangle::new()
1151            .set("x", progress_x)
1152            .set("y", var_y + 15)
1153            .set("width", progress_fill_width)
1154            .set("height", 8)
1155            .set("fill", start_color) // Enhanced with gradient colors
1156            .set("rx", 4)
1157            .set("ry", 4);
1158        matrix_group = matrix_group.add(progress_fill);
1159
1160        // VERTICAL LAYOUT - Size display below progress bar to avoid overlap
1161        let size_display = format!(
1162            "{} / {}",
1163            format_bytes(var.size),
1164            format_bytes(max_size_in_scope)
1165        );
1166        let size_label = SvgText::new(size_display)
1167            .set("x", progress_x + progress_bar_width + 10)
1168            .set("y", var_y + 20)
1169            .set("font-size", 8)
1170            .set("font-weight", "bold")
1171            .set("fill", "#E2E8F0");
1172        matrix_group = matrix_group.add(size_label);
1173
1174        // LIFETIME on separate line to prevent overlap
1175        let time_label = SvgText::new(format!("Active {duration_ms}ms"))
1176            .set("x", 20)
1177            .set("y", var_y + 30)
1178            .set("font-size", 7)
1179            .set("fill", "#FCD34D")
1180            .set("font-weight", "500");
1181        matrix_group = matrix_group.add(time_label);
1182    }
1183
1184    // Show "more" indicator if needed
1185    if vars.len() > 4 {
1186        let more_text = format!("+ {} more variables", vars.len() - 4);
1187        let more_label = SvgText::new(more_text)
1188            .set("x", 20)
1189            .set("y", var_start_y + (4 * var_spacing) + 10)
1190            .set("font-size", 9)
1191            .set("font-weight", "500")
1192            .set("fill", "#94A3B8")
1193            .set("font-style", "italic");
1194        matrix_group = matrix_group.add(more_label);
1195    }
1196
1197    // INTUITIVE EXPLANATION at bottom of matrix - ENGLISH ONLY
1198    let explanation_y = actual_height - 15;
1199    let explanation_text = "Progress Bar: Current Size / Max Size in Scope";
1200    let explanation = SvgText::new(explanation_text)
1201        .set("x", 15)
1202        .set("y", explanation_y)
1203        .set("font-size", 8)
1204        .set("font-weight", "500")
1205        .set("fill", "#FCD34D")
1206        .set("font-style", "italic");
1207    matrix_group = matrix_group.add(explanation);
1208
1209    document = document.add(matrix_group);
1210    Ok(document)
1211}
1212
1213/// Identify precise scope for allocation
1214fn identify_precise_scope(allocation: &AllocationInfo) -> String {
1215    if let Some(var_name) = &allocation.var_name {
1216        if var_name.contains("global") {
1217            return "Global".to_string();
1218        }
1219        // Use timestamp to infer scope
1220        match allocation.timestamp_alloc {
1221            0..=1000 => "Global".to_string(),
1222            1001..=2000 => "demonstrate_builtin_types".to_string(),
1223            2001..=3000 => "demonstrate_smart_pointers".to_string(),
1224            3001..=4000 => "demonstrate_custom_structures".to_string(),
1225            4001..=5000 => "demonstrate_complex_patterns".to_string(),
1226            5001..=6000 => "simulate_web_server_scenario".to_string(),
1227            _ => "simulate_data_processing_pipeline".to_string(),
1228        }
1229    } else {
1230        "Global".to_string()
1231    }
1232}
1233
1234/// Estimate variable duration
1235fn estimate_variable_duration(var: &AllocationInfo) -> u64 {
1236    let base_duration = match var.size {
1237        0..=100 => 10,
1238        101..=1000 => 50,
1239        1001..=10000 => 100,
1240        _ => 200,
1241    };
1242
1243    let type_multiplier = if let Some(type_name) = &var.type_name {
1244        if type_name.contains("Vec") || type_name.contains("HashMap") {
1245            2.0
1246        } else if type_name.contains("Box") || type_name.contains("Rc") {
1247            1.5
1248        } else {
1249            1.0
1250        }
1251    } else {
1252        1.0
1253    };
1254
1255    (base_duration as f64 * type_multiplier) as u64
1256}
1257
1258// ============================================================================
1259// Core SVG Generation Functions (moved from export_enhanced.rs for consolidation)
1260// ============================================================================
1261
1262use crate::core::types::TypeMemoryUsage;
1263
1264// Using EnhancedTypeInfo from export_enhanced module
1265
1266/// Enhanced type information processing with variable names and inner type extraction
1267pub fn enhance_type_information(
1268    memory_by_type: &[TypeMemoryUsage],
1269    allocations: &[AllocationInfo],
1270) -> Vec<crate::export::export_enhanced::EnhancedTypeInfo> {
1271    let mut enhanced_types = Vec::new();
1272
1273    for usage in memory_by_type {
1274        // Skip unknown types
1275        if usage.type_name == "Unknown" {
1276            continue;
1277        }
1278
1279        // Use enhanced type analysis for better categorization
1280        let (simplified_name, category, subcategory) =
1281            analyze_type_with_detailed_subcategory(&usage.type_name);
1282
1283        // Collect variable names for this type
1284        let variable_names: Vec<String> = allocations
1285            .iter()
1286            .filter_map(|alloc| {
1287                if let (Some(var_name), Some(type_name)) = (&alloc.var_name, &alloc.type_name) {
1288                    let (alloc_simplified, _, _) =
1289                        analyze_type_with_detailed_subcategory(type_name);
1290                    if alloc_simplified == simplified_name {
1291                        Some(var_name)
1292                    } else {
1293                        None
1294                    }
1295                } else {
1296                    None
1297                }
1298            })
1299            .take(5)
1300            .cloned()
1301            .collect();
1302
1303        // Add the main type with subcategory information
1304        enhanced_types.push(crate::export::export_enhanced::EnhancedTypeInfo {
1305            simplified_name,
1306            category,
1307            subcategory,
1308            total_size: usage.total_size,
1309            allocation_count: usage.allocation_count,
1310            variable_names,
1311        });
1312    }
1313
1314    enhanced_types
1315}
1316
1317/// Enhanced type analysis with detailed subcategory detection
1318fn analyze_type_with_detailed_subcategory(type_name: &str) -> (String, String, String) {
1319    let clean_type = type_name.trim();
1320
1321    // Handle empty or explicitly unknown types first
1322    if clean_type.is_empty() || clean_type == "Unknown" {
1323        return (
1324            "Unknown Type".to_string(),
1325            "Unknown".to_string(),
1326            "Other".to_string(),
1327        );
1328    }
1329
1330    // Collections analysis
1331    if clean_type.contains("Vec<") || clean_type.contains("vec::Vec") {
1332        return (
1333            "Vec<T>".to_string(),
1334            "Collections".to_string(),
1335            "Vec<T>".to_string(),
1336        );
1337    }
1338
1339    if clean_type.contains("HashMap") {
1340        return (
1341            "HashMap<K,V>".to_string(),
1342            "Collections".to_string(),
1343            "HashMap<K,V>".to_string(),
1344        );
1345    }
1346
1347    if clean_type.contains("String") {
1348        return (
1349            "String".to_string(),
1350            "Basic Types".to_string(),
1351            "Strings".to_string(),
1352        );
1353    }
1354
1355    // Integer types
1356    if clean_type == "i32" || clean_type.ends_with("::i32") {
1357        return (
1358            "i32".to_string(),
1359            "Basic Types".to_string(),
1360            "Integers".to_string(),
1361        );
1362    }
1363
1364    if clean_type == "u8" || clean_type.ends_with("::u8") {
1365        return (
1366            "u8".to_string(),
1367            "Basic Types".to_string(),
1368            "Integers".to_string(),
1369        );
1370    }
1371
1372    // Default case
1373    (
1374        clean_type.to_string(),
1375        "Other".to_string(),
1376        "Custom".to_string(),
1377    )
1378}
1379
1380/// Categorize enhanced allocations by type category - delegate to export_enhanced
1381pub fn categorize_enhanced_allocations(
1382    enhanced_types: &[crate::export::export_enhanced::EnhancedTypeInfo],
1383) -> Vec<crate::export::export_enhanced::AllocationCategory> {
1384    crate::export::export_enhanced::categorize_enhanced_allocations(enhanced_types)
1385}
1386
1387/// Export comprehensive unsafe/FFI memory analysis to dedicated SVG
1388pub fn export_unsafe_ffi_dashboard<P: AsRef<Path>>(
1389    tracker: &UnsafeFFITracker,
1390    path: P,
1391) -> TrackingResult<()> {
1392    let path = path.as_ref();
1393    tracing::info!("Exporting unsafe/FFI dashboard to: {}", path.display());
1394
1395    if let Some(parent) = path.parent() {
1396        if !parent.exists() {
1397            std::fs::create_dir_all(parent)?;
1398        }
1399    }
1400
1401    let enhanced_allocations = tracker.get_enhanced_allocations()?;
1402    let safety_violations = tracker.get_safety_violations()?;
1403
1404    let document = create_unsafe_ffi_dashboard(&enhanced_allocations, &safety_violations)?;
1405
1406    let mut file = File::create(path)?;
1407    svg::write(&mut file, &document)
1408        .map_err(|e| TrackingError::SerializationError(format!("Failed to write SVG: {e}")))?;
1409
1410    tracing::info!("Successfully exported unsafe/FFI dashboard SVG");
1411    Ok(())
1412}
1413
1414/// Create the main unsafe/FFI analysis dashboard
1415fn create_unsafe_ffi_dashboard(
1416    allocations: &[EnhancedAllocationInfo],
1417    violations: &[SafetyViolation],
1418) -> TrackingResult<Document> {
1419    let width = 1400;
1420    let height = 1000;
1421
1422    let mut document = Document::new()
1423        .set("viewBox", (0, 0, width, height))
1424        .set("width", width)
1425        .set("height", height)
1426        .set("style", "background: linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #2c3e50 100%); font-family: 'Segoe UI', Arial, sans-serif;");
1427
1428    // Add definitions for gradients and markers
1429    document = add_svg_definitions(document);
1430
1431    // Header with title and key metrics
1432    document = add_dashboard_header(document, allocations, violations)?;
1433
1434    // Main content areas
1435    document = add_allocation_source_breakdown(document, allocations)?;
1436    document = add_memory_safety_status(document, violations)?;
1437    document = add_boundary_crossing_flow(document, allocations)?;
1438    document = add_unsafe_hotspots(document, allocations)?;
1439
1440    Ok(document)
1441}
1442
1443/// Add SVG definitions for gradients, markers, etc.
1444fn add_svg_definitions(document: Document) -> Document {
1445    let mut defs = Definitions::new();
1446
1447    // Arrow marker for flow diagrams
1448    let arrow_marker = Marker::new()
1449        .set("id", "arrowhead")
1450        .set("markerWidth", 10)
1451        .set("markerHeight", 7)
1452        .set("refX", 9)
1453        .set("refY", 3.5)
1454        .set("orient", "auto")
1455        .add(
1456            Polygon::new()
1457                .set("points", "0 0, 10 3.5, 0 7")
1458                .set("fill", "#e74c3c"),
1459        );
1460    defs = defs.add(arrow_marker);
1461
1462    document.add(defs)
1463}
1464
1465/// Add dashboard header with title and key metrics
1466fn add_dashboard_header(
1467    mut document: Document,
1468    allocations: &[EnhancedAllocationInfo],
1469    violations: &[SafetyViolation],
1470) -> TrackingResult<Document> {
1471    // Main title
1472    let title = SvgText::new("Unsafe Rust & FFI Memory Analysis Dashboard")
1473        .set("x", 700)
1474        .set("y", 40)
1475        .set("text-anchor", "middle")
1476        .set("font-size", 24)
1477        .set("font-weight", "bold")
1478        .set("fill", "#ecf0f1");
1479    document = document.add(title);
1480
1481    // Calculate key metrics
1482    let unsafe_count = allocations
1483        .iter()
1484        .filter(|a| matches!(a.source, AllocationSource::UnsafeRust { .. }))
1485        .count();
1486    let ffi_count = allocations
1487        .iter()
1488        .filter(|a| matches!(a.source, AllocationSource::FfiC { .. }))
1489        .count();
1490    let cross_boundary_events: usize = allocations
1491        .iter()
1492        .map(|a| a.cross_boundary_events.len())
1493        .sum();
1494    let total_unsafe_memory: usize = allocations
1495        .iter()
1496        .filter(|a| !matches!(a.source, AllocationSource::RustSafe))
1497        .map(|a| a.base.size)
1498        .sum();
1499
1500    // Metrics cards
1501    let metrics = [
1502        ("Unsafe Allocations", unsafe_count, "#e74c3c"),
1503        ("FFI Allocations", ffi_count, "#3498db"),
1504        ("Boundary Crossings", cross_boundary_events, "#f39c12"),
1505        ("Safety Violations", violations.len(), "#e67e22"),
1506        ("Unsafe Memory", total_unsafe_memory, "#9b59b6"),
1507    ];
1508
1509    for (i, (label, value, color)) in metrics.iter().enumerate() {
1510        let x = 100 + i as i32 * 250;
1511        let y = 80;
1512
1513        // Card background
1514        let card = Rectangle::new()
1515            .set("x", x - 60)
1516            .set("y", y - 25)
1517            .set("width", 120)
1518            .set("height", 50)
1519            .set("fill", *color)
1520            .set("fill-opacity", 0.2)
1521            .set("stroke", *color)
1522            .set("stroke-width", 2)
1523            .set("rx", 8);
1524        document = document.add(card);
1525
1526        // Value
1527        let value_text = SvgText::new(value.to_string())
1528            .set("x", x)
1529            .set("y", y - 5)
1530            .set("text-anchor", "middle")
1531            .set("font-size", 16)
1532            .set("font-weight", "bold")
1533            .set("fill", *color);
1534        document = document.add(value_text);
1535
1536        // Label
1537        let label_text = SvgText::new(*label)
1538            .set("x", x)
1539            .set("y", y + 15)
1540            .set("text-anchor", "middle")
1541            .set("font-size", 10)
1542            .set("fill", "#bdc3c7");
1543        document = document.add(label_text);
1544    }
1545
1546    Ok(document)
1547}
1548
1549/// Add allocation source breakdown visualization
1550fn add_allocation_source_breakdown(
1551    mut document: Document,
1552    allocations: &[EnhancedAllocationInfo],
1553) -> TrackingResult<Document> {
1554    let start_x = 50;
1555    let start_y = 150;
1556    let width = 600;
1557    let height = 300;
1558
1559    // Section title
1560    let title = SvgText::new("Memory Allocation Sources")
1561        .set("x", start_x + width / 2)
1562        .set("y", start_y - 10)
1563        .set("text-anchor", "middle")
1564        .set("font-size", 18)
1565        .set("font-weight", "bold")
1566        .set("fill", "#ecf0f1");
1567    document = document.add(title);
1568
1569    // Background
1570    let bg = Rectangle::new()
1571        .set("x", start_x)
1572        .set("y", start_y)
1573        .set("width", width)
1574        .set("height", height)
1575        .set("fill", "rgba(52, 73, 94, 0.3)")
1576        .set("stroke", "#34495e")
1577        .set("stroke-width", 2)
1578        .set("rx", 10);
1579    document = document.add(bg);
1580
1581    // Count allocations by source
1582    let mut safe_count = 0;
1583    let mut unsafe_count = 0;
1584    let mut ffi_count = 0;
1585    let mut cross_boundary_count = 0;
1586
1587    for allocation in allocations {
1588        match &allocation.source {
1589            AllocationSource::RustSafe => safe_count += 1,
1590            AllocationSource::UnsafeRust { .. } => unsafe_count += 1,
1591            AllocationSource::FfiC { .. } => ffi_count += 1,
1592            AllocationSource::CrossBoundary { .. } => cross_boundary_count += 1,
1593        }
1594    }
1595
1596    let total = safe_count + unsafe_count + ffi_count + cross_boundary_count;
1597    if total == 0 {
1598        let no_data = SvgText::new("No allocation data available")
1599            .set("x", start_x + width / 2)
1600            .set("y", start_y + height / 2)
1601            .set("text-anchor", "middle")
1602            .set("font-size", 14)
1603            .set("fill", "#95a5a6");
1604        document = document.add(no_data);
1605        return Ok(document);
1606    }
1607
1608    // Create simple bar chart instead of pie chart
1609    let sources = [
1610        ("Safe Rust", safe_count, "#2ecc71"),
1611        ("Unsafe Rust", unsafe_count, "#e74c3c"),
1612        ("FFI", ffi_count, "#3498db"),
1613        ("Cross-boundary", cross_boundary_count, "#9b59b6"),
1614    ];
1615
1616    for (i, (label, count, color)) in sources.iter().enumerate() {
1617        if *count > 0 {
1618            let x = start_x + 50 + i as i32 * 120;
1619            let y = start_y + 200;
1620            let bar_height = (*count as f32 / total as f32 * 100.0) as i32;
1621
1622            // Bar
1623            let bar = Rectangle::new()
1624                .set("x", x)
1625                .set("y", y - bar_height)
1626                .set("width", 40)
1627                .set("height", bar_height)
1628                .set("fill", *color);
1629            document = document.add(bar);
1630
1631            // Count label
1632            let count_text = SvgText::new(count.to_string())
1633                .set("x", x + 20)
1634                .set("y", y - bar_height - 5)
1635                .set("text-anchor", "middle")
1636                .set("font-size", 12)
1637                .set("font-weight", "bold")
1638                .set("fill", *color);
1639            document = document.add(count_text);
1640
1641            // Label
1642            let label_text = SvgText::new(*label)
1643                .set("x", x + 20)
1644                .set("y", y + 20)
1645                .set("text-anchor", "middle")
1646                .set("font-size", 10)
1647                .set("fill", "#ecf0f1");
1648            document = document.add(label_text);
1649        }
1650    }
1651
1652    Ok(document)
1653}
1654
1655/// Add memory safety status panel
1656fn add_memory_safety_status(
1657    mut document: Document,
1658    violations: &[SafetyViolation],
1659) -> TrackingResult<Document> {
1660    let start_x = 750;
1661    let start_y = 150;
1662    let width = 600;
1663    let height = 300;
1664
1665    // Section title
1666    let title = SvgText::new("Memory Safety Status")
1667        .set("x", start_x + width / 2)
1668        .set("y", start_y - 10)
1669        .set("text-anchor", "middle")
1670        .set("font-size", 18)
1671        .set("font-weight", "bold")
1672        .set("fill", "#ecf0f1");
1673    document = document.add(title);
1674
1675    // Background
1676    let bg_color = if violations.is_empty() {
1677        "#27ae60"
1678    } else {
1679        "#e74c3c"
1680    };
1681    let bg = Rectangle::new()
1682        .set("x", start_x)
1683        .set("y", start_y)
1684        .set("width", width)
1685        .set("height", height)
1686        .set("fill", format!("{bg_color}20"))
1687        .set("stroke", bg_color)
1688        .set("stroke-width", 2)
1689        .set("rx", 10);
1690    document = document.add(bg);
1691
1692    if violations.is_empty() {
1693        // Safe status
1694        let safe_text = SvgText::new("No Safety Violations Detected")
1695            .set("x", start_x + width / 2)
1696            .set("y", start_y + 150)
1697            .set("text-anchor", "middle")
1698            .set("font-size", 16)
1699            .set("font-weight", "bold")
1700            .set("fill", "#27ae60");
1701        document = document.add(safe_text);
1702
1703        let safe_desc =
1704            SvgText::new("All unsafe operations and FFI calls appear to be memory-safe")
1705                .set("x", start_x + width / 2)
1706                .set("y", start_y + 180)
1707                .set("text-anchor", "middle")
1708                .set("font-size", 12)
1709                .set("fill", "#2ecc71");
1710        document = document.add(safe_desc);
1711    } else {
1712        // Violations detected
1713        let violation_text =
1714            SvgText::new(format!("{} Safety Violations Detected", violations.len()))
1715                .set("x", start_x + width / 2)
1716                .set("y", start_y + 120)
1717                .set("text-anchor", "middle")
1718                .set("font-size", 16)
1719                .set("font-weight", "bold")
1720                .set("fill", "#e74c3c");
1721        document = document.add(violation_text);
1722
1723        // List violations
1724        for (i, violation) in violations.iter().take(5).enumerate() {
1725            let y = start_y + 160 + i as i32 * 20;
1726
1727            let description = match violation {
1728                SafetyViolation::DoubleFree { .. } => "Double Free",
1729                SafetyViolation::InvalidFree { .. } => "Invalid Free",
1730                SafetyViolation::PotentialLeak { .. } => "Memory Leak",
1731                SafetyViolation::CrossBoundaryRisk { .. } => "Cross-Boundary Risk",
1732            };
1733
1734            let violation_item = SvgText::new(format!("• {description}"))
1735                .set("x", start_x + 30)
1736                .set("y", y)
1737                .set("font-size", 12)
1738                .set("fill", "#e74c3c");
1739            document = document.add(violation_item);
1740        }
1741    }
1742
1743    Ok(document)
1744}
1745
1746/// Add boundary crossing flow diagram
1747fn add_boundary_crossing_flow(
1748    mut document: Document,
1749    allocations: &[EnhancedAllocationInfo],
1750) -> TrackingResult<Document> {
1751    let start_x = 50;
1752    let start_y = 500;
1753    let width = 600;
1754    let height = 200;
1755
1756    // Section title
1757    let title = SvgText::new("Cross-Language Memory Flow")
1758        .set("x", start_x + width / 2)
1759        .set("y", start_y - 10)
1760        .set("text-anchor", "middle")
1761        .set("font-size", 18)
1762        .set("font-weight", "bold")
1763        .set("fill", "#ecf0f1");
1764    document = document.add(title);
1765
1766    // Background
1767    let bg = Rectangle::new()
1768        .set("x", start_x)
1769        .set("y", start_y)
1770        .set("width", width)
1771        .set("height", height)
1772        .set("fill", "rgba(52, 73, 94, 0.3)")
1773        .set("stroke", "#34495e")
1774        .set("stroke-width", 2)
1775        .set("rx", 10);
1776    document = document.add(bg);
1777
1778    // Rust territory
1779    let rust_box = Rectangle::new()
1780        .set("x", start_x + 50)
1781        .set("y", start_y + 50)
1782        .set("width", 200)
1783        .set("height", 100)
1784        .set("fill", "#2ecc71")
1785        .set("fill-opacity", 0.2)
1786        .set("stroke", "#2ecc71")
1787        .set("stroke-width", 2)
1788        .set("rx", 8);
1789    document = document.add(rust_box);
1790
1791    let rust_label = SvgText::new("RUST")
1792        .set("x", start_x + 150)
1793        .set("y", start_y + 110)
1794        .set("text-anchor", "middle")
1795        .set("font-size", 14)
1796        .set("font-weight", "bold")
1797        .set("fill", "#2ecc71");
1798    document = document.add(rust_label);
1799
1800    // FFI territory
1801    let ffi_box = Rectangle::new()
1802        .set("x", start_x + 350)
1803        .set("y", start_y + 50)
1804        .set("width", 200)
1805        .set("height", 100)
1806        .set("fill", "#3498db")
1807        .set("fill-opacity", 0.2)
1808        .set("stroke", "#3498db")
1809        .set("stroke-width", 2)
1810        .set("rx", 8);
1811    document = document.add(ffi_box);
1812
1813    let ffi_label = SvgText::new("FFI / C")
1814        .set("x", start_x + 450)
1815        .set("y", start_y + 110)
1816        .set("text-anchor", "middle")
1817        .set("font-size", 14)
1818        .set("font-weight", "bold")
1819        .set("fill", "#3498db");
1820    document = document.add(ffi_label);
1821
1822    // Count boundary events
1823    let mut rust_to_ffi = 0;
1824    let mut ffi_to_rust = 0;
1825
1826    for allocation in allocations {
1827        for event in &allocation.cross_boundary_events {
1828            match event.event_type {
1829                BoundaryEventType::RustToFfi => rust_to_ffi += 1,
1830                BoundaryEventType::FfiToRust => ffi_to_rust += 1,
1831                BoundaryEventType::OwnershipTransfer => rust_to_ffi += 1,
1832                BoundaryEventType::SharedAccess => {
1833                    rust_to_ffi += 1;
1834                    ffi_to_rust += 1;
1835                }
1836            }
1837        }
1838    }
1839
1840    // Draw flow arrows
1841    if rust_to_ffi > 0 {
1842        let arrow = Line::new()
1843            .set("x1", start_x + 250)
1844            .set("y1", start_y + 80)
1845            .set("x2", start_x + 350)
1846            .set("y2", start_y + 80)
1847            .set("stroke", "#e74c3c")
1848            .set("stroke-width", 3)
1849            .set("marker-end", "url(#arrowhead)");
1850        document = document.add(arrow);
1851
1852        let count_text = SvgText::new(rust_to_ffi.to_string())
1853            .set("x", start_x + 300)
1854            .set("y", start_y + 75)
1855            .set("text-anchor", "middle")
1856            .set("font-size", 12)
1857            .set("font-weight", "bold")
1858            .set("fill", "#e74c3c");
1859        document = document.add(count_text);
1860    }
1861
1862    if ffi_to_rust > 0 {
1863        let count_text = SvgText::new(ffi_to_rust.to_string())
1864            .set("x", start_x + 300)
1865            .set("y", start_y + 135)
1866            .set("text-anchor", "middle")
1867            .set("font-size", 12)
1868            .set("font-weight", "bold")
1869            .set("fill", "#f39c12");
1870        document = document.add(count_text);
1871    }
1872
1873    Ok(document)
1874}
1875
1876/// Add unsafe memory hotspots visualization
1877fn add_unsafe_hotspots(
1878    mut document: Document,
1879    allocations: &[EnhancedAllocationInfo],
1880) -> TrackingResult<Document> {
1881    let start_x = 750;
1882    let start_y = 500;
1883    let width = 600;
1884    let height = 200;
1885
1886    // Section title
1887    let title = SvgText::new("Unsafe Memory Hotspots")
1888        .set("x", start_x + width / 2)
1889        .set("y", start_y - 10)
1890        .set("text-anchor", "middle")
1891        .set("font-size", 18)
1892        .set("font-weight", "bold")
1893        .set("fill", "#ecf0f1");
1894    document = document.add(title);
1895
1896    // Background
1897    let bg = Rectangle::new()
1898        .set("x", start_x)
1899        .set("y", start_y)
1900        .set("width", width)
1901        .set("height", height)
1902        .set("fill", "rgba(52, 73, 94, 0.3)")
1903        .set("stroke", "#34495e")
1904        .set("stroke-width", 2)
1905        .set("rx", 10);
1906    document = document.add(bg);
1907
1908    // Find unsafe allocations
1909    let unsafe_allocations: Vec<_> = allocations
1910        .iter()
1911        .filter(|a| !matches!(a.source, AllocationSource::RustSafe))
1912        .collect();
1913
1914    if unsafe_allocations.is_empty() {
1915        let no_unsafe = SvgText::new("No unsafe memory allocations detected")
1916            .set("x", start_x + width / 2)
1917            .set("y", start_y + height / 2)
1918            .set("text-anchor", "middle")
1919            .set("font-size", 14)
1920            .set("fill", "#2ecc71");
1921        document = document.add(no_unsafe);
1922        return Ok(document);
1923    }
1924
1925    // Display top unsafe allocations
1926    for (i, allocation) in unsafe_allocations.iter().take(6).enumerate() {
1927        let x = start_x + 80 + (i % 3) as i32 * 180;
1928        let y = start_y + 80 + (i / 3) as i32 * 70;
1929
1930        // Hotspot circle
1931        let size_factor = (allocation.base.size.min(1000) as f32 / 1000.0 * 15.0 + 5.0) as i32;
1932        let color = match &allocation.source {
1933            AllocationSource::UnsafeRust { .. } => "#e74c3c",
1934            AllocationSource::FfiC { .. } => "#3498db",
1935            AllocationSource::CrossBoundary { .. } => "#9b59b6",
1936            _ => "#95a5a6",
1937        };
1938
1939        let hotspot = Circle::new()
1940            .set("cx", x)
1941            .set("cy", y)
1942            .set("r", size_factor)
1943            .set("fill", color)
1944            .set("fill-opacity", 0.7)
1945            .set("stroke", color)
1946            .set("stroke-width", 2);
1947        document = document.add(hotspot);
1948
1949        // Size label
1950        let size_text = SvgText::new(format_bytes(allocation.base.size))
1951            .set("x", x)
1952            .set("y", y + 4)
1953            .set("text-anchor", "middle")
1954            .set("font-size", 8)
1955            .set("font-weight", "bold")
1956            .set("fill", "#ffffff");
1957        document = document.add(size_text);
1958
1959        // Type label
1960        let type_label = match &allocation.source {
1961            AllocationSource::UnsafeRust { .. } => "UNSAFE",
1962            AllocationSource::FfiC { .. } => "FFI",
1963            AllocationSource::CrossBoundary { .. } => "CROSS",
1964            _ => "OTHER",
1965        };
1966
1967        let type_text = SvgText::new(type_label)
1968            .set("x", x)
1969            .set("y", y + 35)
1970            .set("text-anchor", "middle")
1971            .set("font-size", 10)
1972            .set("fill", color);
1973        document = document.add(type_text);
1974    }
1975
1976    Ok(document)
1977}
1978
1979#[cfg(test)]
1980mod tests {
1981    use super::*;
1982    use crate::core::tracker::MemoryTracker;
1983    use crate::core::types::{AllocationInfo, MemoryStats, TypeMemoryUsage};
1984    use tempfile::TempDir;
1985
1986    fn create_test_allocation(
1987        ptr: usize,
1988        size: usize,
1989        type_name: Option<String>,
1990        var_name: Option<String>,
1991    ) -> AllocationInfo {
1992        let mut allocation = AllocationInfo::new(ptr, size);
1993        allocation.type_name = type_name;
1994        allocation.var_name = var_name;
1995        allocation
1996    }
1997
1998    fn create_test_memory_stats() -> MemoryStats {
1999        let mut stats = MemoryStats::new();
2000        stats.total_allocations = 10;
2001        stats.total_deallocations = 5;
2002        stats.active_allocations = 5;
2003        stats.peak_memory = 1024;
2004        stats.active_memory = 512;
2005        stats.total_allocated = 2048;
2006        stats.total_deallocated = 1024;
2007        stats.leaked_allocations = 0;
2008        stats
2009    }
2010
2011    #[test]
2012    fn test_export_memory_analysis_creates_svg_file() {
2013        let temp_dir = TempDir::new().unwrap();
2014        let svg_path = temp_dir.path().join("memory_analysis.svg");
2015
2016        // Create a minimal tracker for testing
2017        let tracker = MemoryTracker::new();
2018
2019        // Test the export function
2020        let result = export_memory_analysis(&tracker, &svg_path);
2021        assert!(result.is_ok());
2022        assert!(svg_path.exists());
2023
2024        // Verify the file contains SVG content
2025        let content = std::fs::read_to_string(&svg_path).unwrap();
2026        assert!(content.contains("<svg"));
2027        assert!(content.contains("</svg>"));
2028    }
2029
2030    #[test]
2031    fn test_export_memory_analysis_creates_parent_directory() {
2032        let temp_dir = TempDir::new().unwrap();
2033        let nested_path = temp_dir
2034            .path()
2035            .join("nested")
2036            .join("dir")
2037            .join("memory_analysis.svg");
2038
2039        let tracker = MemoryTracker::new();
2040        let result = export_memory_analysis(&tracker, &nested_path);
2041
2042        assert!(result.is_ok());
2043        assert!(nested_path.exists());
2044        assert!(nested_path.parent().unwrap().exists());
2045    }
2046
2047    #[test]
2048    fn test_export_lifecycle_timeline_creates_svg_file() {
2049        let temp_dir = TempDir::new().unwrap();
2050        let svg_path = temp_dir.path().join("lifecycle_timeline.svg");
2051
2052        let tracker = MemoryTracker::new();
2053        let result = export_lifecycle_timeline(&tracker, &svg_path);
2054
2055        assert!(result.is_ok());
2056        assert!(svg_path.exists());
2057
2058        let content = std::fs::read_to_string(&svg_path).unwrap();
2059        assert!(content.contains("<svg"));
2060        assert!(content.contains("viewBox"));
2061    }
2062
2063    #[test]
2064    fn test_create_memory_analysis_svg_basic_structure() {
2065        let allocations = vec![
2066            create_test_allocation(
2067                0x1000,
2068                64,
2069                Some("String".to_string()),
2070                Some("test_var".to_string()),
2071            ),
2072            create_test_allocation(
2073                0x2000,
2074                128,
2075                Some("Vec<i32>".to_string()),
2076                Some("test_vec".to_string()),
2077            ),
2078        ];
2079        let stats = create_test_memory_stats();
2080        let tracker = MemoryTracker::new();
2081
2082        let result = create_memory_analysis_svg(&allocations, &stats, &tracker);
2083        assert!(result.is_ok());
2084
2085        let document = result.unwrap();
2086        let svg_string = document.to_string();
2087        assert!(svg_string.contains("viewBox"));
2088        assert!(svg_string.contains("width"));
2089        assert!(svg_string.contains("height"));
2090    }
2091
2092    #[test]
2093    fn test_create_memory_analysis_svg_with_empty_allocations() {
2094        let allocations = vec![];
2095        let stats = create_test_memory_stats();
2096        let tracker = MemoryTracker::new();
2097
2098        let result = create_memory_analysis_svg(&allocations, &stats, &tracker);
2099        assert!(result.is_ok());
2100
2101        let document = result.unwrap();
2102        let svg_string = document.to_string();
2103        assert!(svg_string.contains("<svg"));
2104    }
2105
2106    #[test]
2107    fn test_create_lifecycle_timeline_svg_basic_structure() {
2108        let allocations = vec![
2109            create_test_allocation(
2110                0x1000,
2111                64,
2112                Some("String".to_string()),
2113                Some("var1".to_string()),
2114            ),
2115            create_test_allocation(
2116                0x2000,
2117                128,
2118                Some("i32".to_string()),
2119                Some("var2".to_string()),
2120            ),
2121        ];
2122        let stats = create_test_memory_stats();
2123
2124        let result = create_lifecycle_timeline_svg(&allocations, &stats);
2125        assert!(result.is_ok());
2126
2127        let document = result.unwrap();
2128        let svg_string = document.to_string();
2129        assert!(svg_string.contains("viewBox=\"0 0 1600 1200\""));
2130        assert!(svg_string.contains("timeline-bar"));
2131    }
2132
2133    #[test]
2134    fn test_create_lifecycle_timeline_svg_with_empty_allocations() {
2135        let allocations = vec![];
2136        let stats = create_test_memory_stats();
2137
2138        let result = create_lifecycle_timeline_svg(&allocations, &stats);
2139        assert!(result.is_ok());
2140
2141        let document = result.unwrap();
2142        let svg_string = document.to_string();
2143        assert!(svg_string.contains("<svg"));
2144    }
2145
2146    #[test]
2147    fn test_enhance_type_information_with_basic_types() {
2148        let memory_by_type = vec![
2149            TypeMemoryUsage {
2150                type_name: "String".to_string(),
2151                total_size: 256,
2152                allocation_count: 4,
2153                average_size: 64.0,
2154                peak_size: 128,
2155                current_size: 256,
2156                efficiency_score: 0.8,
2157            },
2158            TypeMemoryUsage {
2159                type_name: "Vec<i32>".to_string(),
2160                total_size: 512,
2161                allocation_count: 2,
2162                average_size: 256.0,
2163                peak_size: 512,
2164                current_size: 512,
2165                efficiency_score: 0.9,
2166            },
2167            TypeMemoryUsage {
2168                type_name: "Unknown".to_string(),
2169                total_size: 64,
2170                allocation_count: 1,
2171                average_size: 64.0,
2172                peak_size: 64,
2173                current_size: 64,
2174                efficiency_score: 0.5,
2175            },
2176        ];
2177
2178        let allocations = vec![
2179            create_test_allocation(
2180                0x1000,
2181                64,
2182                Some("String".to_string()),
2183                Some("str1".to_string()),
2184            ),
2185            create_test_allocation(
2186                0x2000,
2187                128,
2188                Some("String".to_string()),
2189                Some("str2".to_string()),
2190            ),
2191            create_test_allocation(
2192                0x3000,
2193                256,
2194                Some("Vec<i32>".to_string()),
2195                Some("vec1".to_string()),
2196            ),
2197        ];
2198
2199        let enhanced = enhance_type_information(&memory_by_type, &allocations);
2200
2201        // Should skip "Unknown" type
2202        assert_eq!(enhanced.len(), 2);
2203
2204        // Check String type
2205        let string_type = enhanced
2206            .iter()
2207            .find(|t| t.simplified_name == "String")
2208            .unwrap();
2209        assert_eq!(string_type.category, "Basic Types");
2210        assert_eq!(string_type.subcategory, "Strings");
2211        assert_eq!(string_type.total_size, 256);
2212        assert_eq!(string_type.allocation_count, 4);
2213        assert_eq!(string_type.variable_names.len(), 2);
2214        assert!(string_type.variable_names.contains(&"str1".to_string()));
2215        assert!(string_type.variable_names.contains(&"str2".to_string()));
2216
2217        // Check Vec type
2218        let vec_type = enhanced
2219            .iter()
2220            .find(|t| t.simplified_name == "Vec<T>")
2221            .unwrap();
2222        assert_eq!(vec_type.category, "Collections");
2223        assert_eq!(vec_type.subcategory, "Vec<T>");
2224        assert_eq!(vec_type.total_size, 512);
2225        assert_eq!(vec_type.allocation_count, 2);
2226    }
2227
2228    #[test]
2229    fn test_enhance_type_information_with_empty_input() {
2230        let memory_by_type = vec![];
2231        let allocations = vec![];
2232
2233        let enhanced = enhance_type_information(&memory_by_type, &allocations);
2234        assert!(enhanced.is_empty());
2235    }
2236
2237    #[test]
2238    fn test_analyze_type_with_detailed_subcategory_collections() {
2239        let (name, category, subcategory) = analyze_type_with_detailed_subcategory("Vec<String>");
2240        assert_eq!(name, "Vec<T>");
2241        assert_eq!(category, "Collections");
2242        assert_eq!(subcategory, "Vec<T>");
2243
2244        let (name, category, subcategory) =
2245            analyze_type_with_detailed_subcategory("std::vec::Vec<i32>");
2246        assert_eq!(name, "Vec<T>");
2247        assert_eq!(category, "Collections");
2248        assert_eq!(subcategory, "Vec<T>");
2249
2250        let (name, category, subcategory) =
2251            analyze_type_with_detailed_subcategory("HashMap<String, i32>");
2252        assert_eq!(name, "HashMap<K,V>");
2253        assert_eq!(category, "Collections");
2254        assert_eq!(subcategory, "HashMap<K,V>");
2255    }
2256
2257    #[test]
2258    fn test_analyze_type_with_detailed_subcategory_basic_types() {
2259        let (name, category, subcategory) = analyze_type_with_detailed_subcategory("String");
2260        assert_eq!(name, "String");
2261        assert_eq!(category, "Basic Types");
2262        assert_eq!(subcategory, "Strings");
2263
2264        let (name, category, subcategory) = analyze_type_with_detailed_subcategory("i32");
2265        assert_eq!(name, "i32");
2266        assert_eq!(category, "Basic Types");
2267        assert_eq!(subcategory, "Integers");
2268
2269        let (name, category, subcategory) = analyze_type_with_detailed_subcategory("u8");
2270        assert_eq!(name, "u8");
2271        assert_eq!(category, "Basic Types");
2272        assert_eq!(subcategory, "Integers");
2273    }
2274
2275    #[test]
2276    fn test_analyze_type_with_detailed_subcategory_unknown_types() {
2277        let (name, category, subcategory) = analyze_type_with_detailed_subcategory("");
2278        assert_eq!(name, "Unknown Type");
2279        assert_eq!(category, "Unknown");
2280        assert_eq!(subcategory, "Other");
2281
2282        let (name, category, subcategory) = analyze_type_with_detailed_subcategory("Unknown");
2283        assert_eq!(name, "Unknown Type");
2284        assert_eq!(category, "Unknown");
2285        assert_eq!(subcategory, "Other");
2286
2287        let (name, category, subcategory) = analyze_type_with_detailed_subcategory("CustomStruct");
2288        assert_eq!(name, "CustomStruct");
2289        assert_eq!(category, "Other");
2290        assert_eq!(subcategory, "Custom");
2291    }
2292
2293    #[test]
2294    fn test_categorize_enhanced_allocations() {
2295        let enhanced_types = vec![
2296            crate::export::export_enhanced::EnhancedTypeInfo {
2297                simplified_name: "String".to_string(),
2298                category: "Basic Types".to_string(),
2299                subcategory: "Strings".to_string(),
2300                total_size: 256,
2301                allocation_count: 2,
2302                variable_names: vec!["str1".to_string(), "str2".to_string()],
2303            },
2304            crate::export::export_enhanced::EnhancedTypeInfo {
2305                simplified_name: "Vec<T>".to_string(),
2306                category: "Collections".to_string(),
2307                subcategory: "Vec<T>".to_string(),
2308                total_size: 512,
2309                allocation_count: 1,
2310                variable_names: vec!["vec1".to_string()],
2311            },
2312        ];
2313
2314        let categorized = categorize_enhanced_allocations(&enhanced_types);
2315
2316        // categorize_enhanced_allocations returns Vec<AllocationCategory>
2317        assert_eq!(categorized.len(), 2);
2318
2319        // Find Basic Types category
2320        let basic_types_category = categorized.iter().find(|c| c.name == "Basic Types");
2321        assert!(basic_types_category.is_some());
2322        let basic_types = basic_types_category.unwrap();
2323        assert_eq!(basic_types.allocations.len(), 1);
2324        assert_eq!(basic_types.total_size, 256);
2325
2326        // Find Collections category
2327        let collections_category = categorized.iter().find(|c| c.name == "Collections");
2328        assert!(collections_category.is_some());
2329        let collections = collections_category.unwrap();
2330        assert_eq!(collections.allocations.len(), 1);
2331        assert_eq!(collections.total_size, 512);
2332    }
2333
2334    #[test]
2335    fn test_calculate_dynamic_matrix_size_small_count() {
2336        let (width, height) = calculate_dynamic_matrix_size(3);
2337        assert!(width >= 250); // Minimum width protection
2338        assert!(height >= 150); // Minimum height protection
2339        assert!(width <= 350); // Should be smaller than standard for fewer variables
2340    }
2341
2342    #[test]
2343    fn test_calculate_dynamic_matrix_size_standard_count() {
2344        let (width, height) = calculate_dynamic_matrix_size(5);
2345        assert_eq!(width, 350); // Standard width
2346        assert_eq!(height, 320); // Standard height
2347    }
2348
2349    #[test]
2350    fn test_calculate_dynamic_matrix_size_large_count() {
2351        let (width, height) = calculate_dynamic_matrix_size(10);
2352        assert_eq!(width, 350); // Fixed standard size for large counts
2353        assert_eq!(height, 280); // Fixed standard size for large counts
2354    }
2355
2356    #[test]
2357    fn test_calculate_scope_lifetime_empty_vars() {
2358        let vars: Vec<&AllocationInfo> = vec![];
2359        let lifetime = calculate_scope_lifetime("test_scope", &vars);
2360        assert_eq!(lifetime, 0);
2361    }
2362
2363    #[test]
2364    fn test_calculate_scope_lifetime_global_scope() {
2365        let allocation = create_test_allocation(
2366            0x1000,
2367            64,
2368            Some("String".to_string()),
2369            Some("var1".to_string()),
2370        );
2371        let vars = vec![&allocation];
2372
2373        let lifetime = calculate_scope_lifetime("Global", &vars);
2374        assert_eq!(lifetime, 2000); // Should use estimated program runtime
2375    }
2376
2377    #[test]
2378    fn test_calculate_scope_lifetime_local_scope_single_var() {
2379        let allocation = create_test_allocation(
2380            0x1000,
2381            64,
2382            Some("String".to_string()),
2383            Some("var1".to_string()),
2384        );
2385        let vars = vec![&allocation];
2386
2387        let lifetime = calculate_scope_lifetime("local_scope", &vars);
2388        assert_eq!(lifetime, 100); // Default for single variable
2389    }
2390
2391    #[test]
2392    fn test_calculate_scope_lifetime_local_scope_multiple_vars() {
2393        let mut allocation1 = create_test_allocation(
2394            0x1000,
2395            64,
2396            Some("String".to_string()),
2397            Some("var1".to_string()),
2398        );
2399        let mut allocation2 = create_test_allocation(
2400            0x2000,
2401            128,
2402            Some("i32".to_string()),
2403            Some("var2".to_string()),
2404        );
2405
2406        // Set different timestamps
2407        allocation1.timestamp_alloc = 1000;
2408        allocation2.timestamp_alloc = 1500;
2409
2410        let vars = vec![&allocation1, &allocation2];
2411        let lifetime = calculate_scope_lifetime("local_scope", &vars);
2412        assert_eq!(lifetime, 500); // Difference between timestamps
2413    }
2414
2415    #[test]
2416    fn test_estimate_program_runtime() {
2417        let runtime = estimate_program_runtime();
2418        assert_eq!(runtime, 2000); // Should return 2 seconds
2419    }
2420
2421    #[test]
2422    fn test_get_scope_background_color() {
2423        assert_eq!(
2424            get_scope_background_color("Global"),
2425            "rgba(52, 73, 94, 0.2)"
2426        );
2427        assert_eq!(
2428            get_scope_background_color("local"),
2429            "rgba(52, 152, 219, 0.2)"
2430        );
2431        assert_eq!(
2432            get_scope_background_color("function"),
2433            "rgba(52, 152, 219, 0.2)"
2434        );
2435    }
2436
2437    #[test]
2438    fn test_get_scope_border_color() {
2439        assert_eq!(get_scope_border_color("Global"), "#34495E");
2440        assert_eq!(get_scope_border_color("local"), "#3498DB");
2441        assert_eq!(get_scope_border_color("function"), "#3498DB");
2442    }
2443
2444    #[test]
2445    fn test_add_relationship_legend() {
2446        let document = Document::new();
2447        let result = add_relationship_legend(document, 100);
2448
2449        assert!(result.is_ok());
2450        let document = result.unwrap();
2451        let svg_string = document.to_string();
2452
2453        // Check that legend items are present
2454        assert!(svg_string.contains("Ownership Transfer"));
2455        assert!(svg_string.contains("Mutable Borrow"));
2456        assert!(svg_string.contains("Immutable Borrow"));
2457        assert!(svg_string.contains("Clone"));
2458        assert!(svg_string.contains("Shared Pointer"));
2459        assert!(svg_string.contains("Indirect Reference"));
2460    }
2461
2462    #[test]
2463    fn test_enhance_type_information_variable_name_limit() {
2464        let memory_by_type = vec![TypeMemoryUsage {
2465            type_name: "String".to_string(),
2466            total_size: 1000,
2467            allocation_count: 10,
2468            average_size: 100.0,
2469            peak_size: 1000,
2470            current_size: 1000,
2471            efficiency_score: 0.9,
2472        }];
2473
2474        // Create more than 5 allocations to test the limit
2475        let allocations = (0..8)
2476            .map(|i| {
2477                create_test_allocation(
2478                    0x1000 + i * 0x100,
2479                    64,
2480                    Some("String".to_string()),
2481                    Some(format!("var{i}")),
2482                )
2483            })
2484            .collect::<Vec<_>>();
2485
2486        let enhanced = enhance_type_information(&memory_by_type, &allocations);
2487
2488        assert_eq!(enhanced.len(), 1);
2489        let string_type = &enhanced[0];
2490
2491        // Should limit to 5 variable names
2492        assert_eq!(string_type.variable_names.len(), 5);
2493        assert!(string_type.variable_names.contains(&"var0".to_string()));
2494        assert!(string_type.variable_names.contains(&"var4".to_string()));
2495    }
2496
2497    #[test]
2498    fn test_enhance_type_information_with_none_values() {
2499        let memory_by_type = vec![TypeMemoryUsage {
2500            type_name: "String".to_string(),
2501            total_size: 256,
2502            allocation_count: 2,
2503            average_size: 128.0,
2504            peak_size: 256,
2505            current_size: 256,
2506            efficiency_score: 0.5,
2507        }];
2508
2509        let allocations = vec![
2510            create_test_allocation(0x1000, 64, None, None), // No type or var name
2511            create_test_allocation(0x2000, 128, Some("String".to_string()), None), // No var name
2512            create_test_allocation(0x3000, 256, None, Some("var1".to_string())), // No type name
2513        ];
2514
2515        let enhanced = enhance_type_information(&memory_by_type, &allocations);
2516
2517        assert_eq!(enhanced.len(), 1);
2518        let string_type = &enhanced[0];
2519
2520        // Should have no variable names since none match the criteria
2521        assert!(string_type.variable_names.is_empty());
2522    }
2523}