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