import json
import html

def generate_html(source_code, trace_data, output_filename="trace.html"):
    
    # --- Process Source Code ---
    code_lines = source_code.splitlines()
    formatted_code = ""
    for i, line in enumerate(code_lines):
        safe_line = html.escape(line) if line.strip() else "&nbsp;"
        formatted_code += (
            f'<div id="code-line-{i+1}" class="code-line" data-line="{i+1}">'
            f'<div class="gutter">{i+1}</div>'
            f'<div class="content"><code class="language-python">{safe_line}</code></div>'
            f'<div class="arrow-container" id="arrow-{i+1}"></div>'
            f'</div>'
        )

    html_content = f"""
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PyViz Pro - Advanced Python Visualizer</title>
    
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
    
    <style>
        :root {{
            /* Light Mode */
            --bg-primary: #ffffff;
            --bg-secondary: #f8f9fa;
            --bg-tertiary: #e9ecef;
            --text-primary: #1a1a1a;
            --text-secondary: #6c757d;
            --border-color: #dee2e6;
            --highlight-current: #fff3cd;
            --highlight-prev: #d1e7dd;
            --arrow-current: #dc3545;
            --arrow-prev: #198754;
            --frame-bg: #f8f9fa;
            --frame-header: #e9ecef;
            --obj-list-bg: #e7f5ff;
            --obj-dict-bg: #fff3cd;
            --obj-set-bg: #f3e5f5;
            --obj-func-bg: #e8f5e9;
            --shadow: rgba(0, 0, 0, 0.1);
            --accent: #0d6efd;
            --success: #198754;
            --warning: #ffc107;
            --danger: #dc3545;
        }}

        [data-theme="dark"] {{
            /* Dark Mode */
            --bg-primary: #1a1d23;
            --bg-secondary: #22262e;
            --bg-tertiary: #2d3139;
            --text-primary: #e4e6eb;
            --text-secondary: #8b8d94;
            --border-color: #3a3f4b;
            --highlight-current: #3d3d1f;
            --highlight-prev: #1e3a28;
            --arrow-current: #ff6b6b;
            --arrow-prev: #51cf66;
            --frame-bg: #22262e;
            --frame-header: #2d3139;
            --obj-list-bg: #1e3a5f;
            --obj-dict-bg: #3d3d1f;
            --obj-set-bg: #3a2d42;
            --obj-func-bg: #1e3a28;
            --shadow: rgba(0, 0, 0, 0.5);
            --accent: #4dabf7;
            --success: #51cf66;
            --warning: #ffd43b;
            --danger: #ff6b6b;
        }}

        * {{ 
            box-sizing: border-box; 
            margin: 0; 
            padding: 0;
        }}

        body {{
            height: 100vh; 
            width: 100vw;
            background: var(--bg-primary); 
            color: var(--text-primary);
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
            display: flex; 
            flex-direction: column;
            overflow: hidden;
            transition: background 0.3s ease, color 0.3s ease;
        }}

        /* ==================== HEADER ==================== */
        header {{
            padding: 16px 24px;
            background: var(--bg-secondary);
            border-bottom: 1px solid var(--border-color);
            display: flex;
            justify-content: space-between;
            align-items: center;
            box-shadow: 0 2px 8px var(--shadow);
            z-index: 100;
        }}

        .brand {{
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 1.5rem;
            font-weight: 700;
        }}

        .brand i {{
            color: var(--accent);
            animation: pulse 2s ease-in-out infinite;
        }}

        @keyframes pulse {{
            0%, 100% {{ transform: scale(1); }}
            50% {{ transform: scale(1.1); }}
        }}

        .brand-text {{
            background: linear-gradient(135deg, var(--accent), var(--success));
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
        }}

        .header-controls {{
            display: flex;
            align-items: center;
            gap: 16px;
        }}

        .theme-toggle {{
            background: var(--bg-tertiary);
            border: 1px solid var(--border-color);
            padding: 8px 16px;
            border-radius: 20px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 0.9rem;
            transition: all 0.3s ease;
        }}

        .theme-toggle:hover {{
            transform: translateY(-2px);
            box-shadow: 0 4px 12px var(--shadow);
        }}

        .info-badge {{
            background: var(--accent);
            color: white;
            padding: 6px 12px;
            border-radius: 12px;
            font-size: 0.85rem;
            font-weight: 500;
        }}

        /* ==================== MAIN LAYOUT ==================== */
        .container {{ 
            flex: 1; 
            display: flex; 
            overflow: hidden;
            position: relative;
        }}

        /* ==================== SIDEBAR (DS Guide) ==================== */
        .sidebar {{
            width: 280px;
            background: var(--bg-secondary);
            border-right: 1px solid var(--border-color);
            display: flex;
            flex-direction: column;
            overflow-y: auto;
            transition: transform 0.3s ease;
        }}

        .sidebar.collapsed {{
            transform: translateX(-280px);
        }}

        .sidebar-header {{
            padding: 16px;
            background: var(--bg-tertiary);
            border-bottom: 1px solid var(--border-color);
            font-weight: 600;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }}

        .sidebar-content {{
            padding: 16px;
        }}

        .ds-card {{
            background: var(--bg-tertiary);
            border: 1px solid var(--border-color);
            border-radius: 8px;
            padding: 12px;
            margin-bottom: 12px;
            cursor: pointer;
            transition: all 0.2s ease;
        }}

        .ds-card:hover {{
            transform: translateX(4px);
            border-color: var(--accent);
        }}

        .ds-card-title {{
            font-weight: 600;
            margin-bottom: 4px;
            color: var(--accent);
        }}

        .ds-card-desc {{
            font-size: 0.85rem;
            color: var(--text-secondary);
        }}

        .complexity-badge {{
            display: inline-block;
            padding: 2px 8px;
            border-radius: 4px;
            font-size: 0.75rem;
            font-weight: 500;
            margin-top: 6px;
        }}

        .complexity-good {{ background: var(--success); color: white; }}
        .complexity-ok {{ background: var(--warning); color: white; }}
        .complexity-bad {{ background: var(--danger); color: white; }}

        /* ==================== CODE PANEL ==================== */
        .panel-code {{ 
            flex: 1;
            border-right: 2px solid var(--border-color);
            overflow-y: auto;
            background: var(--bg-primary);
            position: relative;
        }}

        .code-header {{
            position: sticky;
            top: 0;
            background: var(--bg-secondary);
            padding: 12px 20px;
            border-bottom: 1px solid var(--border-color);
            font-weight: 600;
            z-index: 10;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }}

        .code-line {{ 
            display: flex; 
            position: relative; 
            font-family: 'JetBrains Mono', 'Fira Code', monospace;
            font-size: 0.9rem;
            line-height: 1.6;
            transition: background 0.2s ease;
        }}

        .gutter {{ 
            min-width: 50px;
            text-align: right;
            padding: 4px 12px;
            color: var(--text-secondary);
            user-select: none;
            background: var(--bg-secondary);
            border-right: 2px solid var(--border-color);
            font-weight: 500;
        }}

        .content {{ 
            padding: 4px 16px;
            flex: 1;
            position: relative;
        }}

        .arrow-container {{ 
            position: absolute;
            left: -24px;
            top: 50%;
            transform: translateY(-50%);
            font-size: 1.2rem;
            font-weight: bold;
            z-index: 5;
            animation: slideIn 0.3s ease;
        }}

        @keyframes slideIn {{
            from {{ opacity: 0; transform: translateY(-50%) translateX(-10px); }}
            to {{ opacity: 1; transform: translateY(-50%) translateX(0); }}
        }}
        
        .highlight-cur {{ 
            background: var(--highlight-current);
            box-shadow: inset 4px 0 0 var(--arrow-current);
        }}

        .highlight-prev {{
            background: var(--highlight-prev);
            opacity: 0.5;
        }}

        .arrow-red {{ 
            color: var(--arrow-current);
        }}

        .arrow-green {{ 
            color: var(--arrow-prev);
        }}

        /* ==================== VIZ PANEL ==================== */
        .panel-viz {{ 
            flex: 1.5;
            display: flex;
            flex-direction: column;
            background: var(--bg-primary);
        }}

        .viz-header {{ 
            padding: 12px 20px;
            background: var(--bg-secondary);
            border-bottom: 1px solid var(--border-color);
            font-weight: 600;
            display: flex;
            justify-content: space-around;
            align-items: center;
        }}

        .viz-header-item {{
            display: flex;
            align-items: center;
            gap: 8px;
        }}

        .viz-header-item i {{
            color: var(--accent);
        }}
        
        .viz-canvas {{ 
            flex: 1;
            display: flex;
            overflow: auto;
            position: relative;
            background: 
                linear-gradient(var(--border-color) 1px, transparent 1px),
                linear-gradient(90deg, var(--border-color) 1px, transparent 1px);
            background-size: 20px 20px;
            background-position: -1px -1px;
        }}

        .col-frames {{ 
            width: 45%;
            padding: 24px;
            border-right: 2px dashed var(--border-color);
            display: flex;
            flex-direction: column;
            gap: 20px;
        }}

        .col-objects {{ 
            flex: 1;
            padding: 24px;
            position: relative;
        }}

        /* ==================== FRAMES ==================== */
        .frame {{ 
            background: var(--frame-bg);
            border: 2px solid var(--border-color);
            border-radius: 12px;
            box-shadow: 0 4px 12px var(--shadow);
            overflow: hidden;
            transition: all 0.3s ease;
            animation: fadeIn 0.4s ease;
        }}

        @keyframes fadeIn {{
            from {{ opacity: 0; transform: translateY(10px); }}
            to {{ opacity: 1; transform: translateY(0); }}
        }}

        .frame:hover {{
            transform: translateY(-4px);
            box-shadow: 0 8px 20px var(--shadow);
        }}

        .frame-head {{ 
            background: var(--frame-header);
            padding: 10px 16px;
            font-weight: 600;
            font-size: 0.9rem;
            border-bottom: 2px solid var(--border-color);
            display: flex;
            align-items: center;
            gap: 8px;
        }}

        .frame-icon {{
            color: var(--accent);
        }}

        .var-table {{ 
            width: 100%;
            font-family: 'JetBrains Mono', monospace;
            font-size: 0.85rem;
        }}

        .var-table td {{ 
            padding: 8px 12px;
            border-bottom: 1px solid var(--border-color);
        }}

        .var-table tr:last-child td {{ 
            border-bottom: none;
        }}

        .var-table tr:hover {{
            background: var(--bg-tertiary);
        }}

        .v-name {{ 
            text-align: right;
            padding-right: 12px;
            color: var(--text-secondary);
            width: 40%;
            font-weight: 500;
            border-right: 2px solid var(--border-color);
        }}

        .v-val {{ 
            text-align: left;
            padding-left: 12px;
            color: var(--text-primary);
            position: relative;
            font-weight: 500;
        }}
        
        .retval-row {{ 
            background: var(--highlight-prev);
        }}

        .retval-label {{ 
            color: var(--success);
            font-weight: 700;
        }}

        /* ==================== OBJECTS ==================== */
        .obj-box {{
            position: absolute;
            border: 2px solid var(--border-color);
            box-shadow: 0 4px 16px var(--shadow);
            padding: 0;
            font-family: 'JetBrains Mono', monospace;
            font-size: 0.8rem;
            min-width: 140px;
            border-radius: 8px;
            overflow: hidden;
            z-index: 10;
            animation: fadeIn 0.4s ease;
            transition: all 0.3s ease;
        }}

        .obj-box:hover {{
            transform: scale(1.05);
            z-index: 20;
        }}

        .obj-header {{ 
            background: var(--frame-header);
            padding: 6px 10px;
            font-size: 0.75rem;
            font-weight: 600;
            border-bottom: 2px solid var(--border-color);
            display: flex;
            justify-content: space-between;
            align-items: center;
        }}

        .obj-type-badge {{
            background: var(--accent);
            color: white;
            padding: 2px 6px;
            border-radius: 4px;
            font-size: 0.7rem;
        }}

        .obj-content {{ 
            padding: 10px;
        }}
        
        /* Data structure specific colors */
        .obj-box[data-ds="array"] {{ background: var(--obj-list-bg); }}
        .obj-box[data-ds="dict"] {{ background: var(--obj-dict-bg); }}
        .obj-box[data-ds="set"] {{ background: var(--obj-set-bg); }}
        .obj-box[data-ds="function"] {{ background: var(--obj-func-bg); }}

        /* List/Array Visualization */
        .list-table {{ 
            border-collapse: collapse;
            background: var(--bg-primary);
            border: 1px solid var(--border-color);
        }}

        .list-table td {{ 
            border: 1px solid var(--border-color);
            padding: 6px 10px;
            text-align: center;
            min-width: 40px;
            font-weight: 500;
        }}

        .idx-row td {{ 
            background: var(--bg-tertiary);
            color: var(--text-secondary);
            font-size: 0.7rem;
            font-weight: 600;
        }}
        
        /* Connector Dots */
        .dot {{
            width: 10px;
            height: 10px;
            background: var(--accent);
            border: 2px solid var(--bg-primary);
            border-radius: 50%;
            display: inline-block;
            cursor: pointer;
            transition: all 0.2s ease;
            box-shadow: 0 0 0 0 var(--accent);
            animation: pulse-dot 2s infinite;
        }}

        @keyframes pulse-dot {{
            0% {{ box-shadow: 0 0 0 0 var(--accent); }}
            50% {{ box-shadow: 0 0 0 4px transparent; }}
            100% {{ box-shadow: 0 0 0 0 transparent; }}
        }}

        .dot:hover {{
            transform: scale(1.5);
            background: var(--success);
        }}

        /* ==================== FOOTER CONTROLS ==================== */
        footer {{
            height: auto;
            background: var(--bg-secondary);
            border-top: 1px solid var(--border-color);
            padding: 16px 24px;
            box-shadow: 0 -2px 8px var(--shadow);
        }}

        .controls-wrapper {{
            display: flex;
            flex-direction: column;
            gap: 16px;
            max-width: 1200px;
            margin: 0 auto;
        }}

        .controls-row {{
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 20px;
        }}

        .control-group {{
            display: flex;
            align-items: center;
            gap: 12px;
        }}

        button {{
            padding: 10px 20px;
            background: var(--accent);
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-family: 'Inter', sans-serif;
            font-weight: 500;
            font-size: 0.9rem;
            transition: all 0.2s ease;
            display: flex;
            align-items: center;
            gap: 8px;
        }}

        button:hover {{
            transform: translateY(-2px);
            box-shadow: 0 4px 12px var(--shadow);
            filter: brightness(1.1);
        }}

        button:active {{
            transform: translateY(0);
        }}

        button.secondary {{
            background: var(--bg-tertiary);
            color: var(--text-primary);
            border: 1px solid var(--border-color);
        }}

        .play-button {{
            background: var(--success);
            padding: 12px 24px;
            font-size: 1rem;
        }}

        .play-button.playing {{
            background: var(--warning);
        }}

        input[type=range] {{
            flex: 1;
            max-width: 500px;
            height: 8px;
            -webkit-appearance: none;
            appearance: none;
            background: var(--bg-tertiary);
            border-radius: 10px;
            outline: none;
            transition: all 0.2s ease;
        }}

        input[type=range]::-webkit-slider-thumb {{
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            background: var(--accent);
            border-radius: 50%;
            cursor: pointer;
            box-shadow: 0 2px 8px var(--shadow);
            transition: all 0.2s ease;
        }}

        input[type=range]::-webkit-slider-thumb:hover {{
            transform: scale(1.2);
        }}

        input[type=range]::-moz-range-thumb {{
            width: 20px;
            height: 20px;
            background: var(--accent);
            border-radius: 50%;
            cursor: pointer;
            border: none;
        }}

        .speed-control {{
            display: flex;
            align-items: center;
            gap: 12px;
            background: var(--bg-tertiary);
            padding: 8px 16px;
            border-radius: 8px;
        }}

        .speed-control label {{
            font-size: 0.85rem;
            font-weight: 500;
            color: var(--text-secondary);
        }}

        .speed-control select {{
            background: var(--bg-primary);
            color: var(--text-primary);
            border: 1px solid var(--border-color);
            padding: 6px 12px;
            border-radius: 6px;
            cursor: pointer;
            font-family: 'Inter', sans-serif;
        }}

        .step-label {{
            font-size: 0.9rem;
            font-weight: 600;
            color: var(--text-primary);
            background: var(--bg-tertiary);
            padding: 8px 16px;
            border-radius: 8px;
            min-width: 150px;
            text-align: center;
        }}

        /* ==================== CONSOLE OUTPUT ==================== */
        .console-output {{
            position: fixed;
            bottom: 120px;
            right: 20px;
            width: 350px;
            max-height: 200px;
            background: var(--bg-secondary);
            border: 2px solid var(--border-color);
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 4px 20px var(--shadow);
            display: none;
            z-index: 1000;
        }}

        .console-output.show {{
            display: block;
            animation: slideUp 0.3s ease;
        }}

        @keyframes slideUp {{
            from {{ transform: translateY(20px); opacity: 0; }}
            to {{ transform: translateY(0); opacity: 1; }}
        }}

        .console-header {{
            background: var(--bg-tertiary);
            padding: 8px 12px;
            border-bottom: 1px solid var(--border-color);
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-weight: 600;
            font-size: 0.85rem;
        }}

        .console-body {{
            padding: 12px;
            font-family: 'JetBrains Mono', monospace;
            font-size: 0.8rem;
            max-height: 150px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-break: break-all;
        }}

        /* ==================== TOGGLE BUTTON ==================== */
        .sidebar-toggle {{
            position: fixed;
            left: 0;
            top: 50%;
            transform: translateY(-50%);
            background: var(--accent);
            color: white;
            border: none;
            padding: 12px 8px;
            border-radius: 0 8px 8px 0;
            cursor: pointer;
            z-index: 99;
            transition: all 0.3s ease;
        }}

        .sidebar-toggle:hover {{
            padding-left: 12px;
        }}

        /* ==================== RESPONSIVE ==================== */
        @media (max-width: 1200px) {{
            .sidebar {{
                position: fixed;
                left: 0;
                top: 0;
                bottom: 0;
                z-index: 999;
                box-shadow: 4px 0 20px var(--shadow);
            }}
        }}

        /* ==================== SCROLLBAR ==================== */
        ::-webkit-scrollbar {{
            width: 10px;
            height: 10px;
        }}

        ::-webkit-scrollbar-track {{
            background: var(--bg-secondary);
        }}

        ::-webkit-scrollbar-thumb {{
            background: var(--border-color);
            border-radius: 5px;
        }}

        ::-webkit-scrollbar-thumb:hover {{
            background: var(--text-secondary);
        }}

    </style>
</head>
<body data-theme="light">

<header>
    <div class="brand">
        <i class="fas fa-code"></i>
        <span class="brand-text">PyViz Pro</span>
    </div>
    <div class="header-controls">
        <div class="info-badge">
            <i class="fas fa-microchip"></i> Python 3.11
        </div>
        <button class="theme-toggle" onclick="toggleTheme()">
            <i class="fas fa-moon" id="theme-icon"></i>
            <span id="theme-text">Dark Mode</span>
        </button>
    </div>
</header>

<button class="sidebar-toggle" onclick="toggleSidebar()">
    <i class="fas fa-chevron-right" id="sidebar-toggle-icon"></i>
</button>

<div class="container">
    <!-- DATA STRUCTURES GUIDE SIDEBAR -->
    <div class="sidebar" id="sidebar">
        <div class="sidebar-header">
            <span><i class="fas fa-book"></i> DS Learning Guide</span>
        </div>
        <div class="sidebar-content">
            <div class="ds-card" onclick="showDSInfo('array')">
                <div class="ds-card-title"><i class="fas fa-list"></i> Arrays / Lists</div>
                <div class="ds-card-desc">Sequential collection with indexed access</div>
                <span class="complexity-badge complexity-good">Access: O(1)</span>
                <span class="complexity-badge complexity-bad">Insert: O(n)</span>
            </div>
            
            <div class="ds-card" onclick="showDSInfo('stack')">
                <div class="ds-card-title"><i class="fas fa-layer-group"></i> Stack</div>
                <div class="ds-card-desc">LIFO (Last In First Out) structure</div>
                <span class="complexity-badge complexity-good">Push/Pop: O(1)</span>
            </div>
            
            <div class="ds-card" onclick="showDSInfo('queue')">
                <div class="ds-card-title"><i class="fas fa-stream"></i> Queue</div>
                <div class="ds-card-desc">FIFO (First In First Out) structure</div>
                <span class="complexity-badge complexity-good">Enqueue/Dequeue: O(1)</span>
            </div>
            
            <div class="ds-card" onclick="showDSInfo('dict')">
                <div class="ds-card-title"><i class="fas fa-hashtag"></i> Hash Table / Dict</div>
                <div class="ds-card-desc">Key-value pairs with fast lookup</div>
                <span class="complexity-badge complexity-good">Search: O(1) avg</span>
            </div>
            
            <div class="ds-card" onclick="showDSInfo('tree')">
                <div class="ds-card-title"><i class="fas fa-project-diagram"></i> Binary Tree</div>
                <div class="ds-card-desc">Hierarchical structure with nodes</div>
                <span class="complexity-badge complexity-ok">Search: O(log n)</span>
            </div>
            
            <div class="ds-card" onclick="showDSInfo('heap')">
                <div class="ds-card-title"><i class="fas fa-mountain"></i> Heap</div>
                <div class="ds-card-desc">Priority queue implementation</div>
                <span class="complexity-badge complexity-ok">Insert: O(log n)</span>
            </div>
            
            <div class="ds-card" onclick="showDSInfo('graph')">
                <div class="ds-card-title"><i class="fas fa-network-wired"></i> Graph</div>
                <div class="ds-card-desc">Vertices connected by edges</div>
                <span class="complexity-badge complexity-bad">BFS/DFS: O(V+E)</span>
            </div>
            
            <div class="ds-card" onclick="showDSInfo('sorting')">
                <div class="ds-card-title"><i class="fas fa-sort"></i> Sorting Algorithms</div>
                <div class="ds-card-desc">Compare sorting techniques</div>
                <span class="complexity-badge complexity-ok">QuickSort: O(n log n)</span>
                <span class="complexity-badge complexity-bad">BubbleSort: O(n²)</span>
            </div>
        </div>
    </div>

    <!-- CODE PANEL -->
    <div class="panel-code" id="code-panel">
        <div class="code-header">
            <span><i class="fas fa-file-code"></i> Source Code</span>
            <span id="current-line-info" class="info-badge">Line: -</span>
        </div>
        {formatted_code}
    </div>

    <!-- VIZ PANEL -->
    <div class="panel-viz">
        <div class="viz-header">
            <div class="viz-header-item">
                <i class="fas fa-layer-group"></i>
                <span>Stack Frames</span>
            </div>
            <div class="viz-header-item">
                <i class="fas fa-database"></i>
                <span>Heap Objects</span>
            </div>
        </div>
        <div class="viz-canvas" id="viz-canvas">
            <div class="col-frames" id="frames-col"></div>
            <div class="col-objects" id="objects-col"></div>
        </div>
    </div>
</div>

<!-- CONSOLE OUTPUT -->
<div class="console-output" id="console-output">
    <div class="console-header">
        <span><i class="fas fa-terminal"></i> Console Output</span>
        <button class="secondary" style="padding: 4px 8px; font-size: 0.75rem;" onclick="toggleConsole()">
            <i class="fas fa-times"></i>
        </button>
    </div>
    <div class="console-body" id="console-body">
        No output yet...
    </div>
</div>

<footer>
    <div class="controls-wrapper">
        <div class="controls-row">
            <div class="control-group">
                <button class="secondary" onclick="firstStep()">
                    <i class="fas fa-step-backward"></i> First
                </button>
                <button class="secondary" onclick="prevStep()">
                    <i class="fas fa-chevron-left"></i> Prev
                </button>
                <button class="play-button" id="play-button" onclick="togglePlay()">
                    <i class="fas fa-play" id="play-icon"></i>
                    <span id="play-text">Play</span>
                </button>
                <button class="secondary" onclick="nextStep()">
                    Next <i class="fas fa-chevron-right"></i>
                </button>
                <button class="secondary" onclick="lastStep()">
                    Last <i class="fas fa-step-forward"></i>
                </button>
            </div>
            
            <div class="control-group">
                <div class="speed-control">
                    <label><i class="fas fa-gauge"></i> Speed:</label>
                    <select id="speed-select" onchange="updateSpeed()">
                        <option value="2000">0.5x</option>
                        <option value="1000" selected>1x</option>
                        <option value="500">2x</option>
                        <option value="250">4x</option>
                        <option value="100">10x</option>
                    </select>
                </div>
                
                <button class="secondary" onclick="toggleConsole()">
                    <i class="fas fa-terminal"></i> Console
                </button>
            </div>
        </div>
        
        <div class="controls-row">
            <div class="control-group" style="flex: 1;">
                <input type="range" id="timeline" min="0" max="0" value="0">
                <div class="step-label" id="step-label">Step 0 of 0</div>
            </div>
        </div>
    </div>
</footer>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leader-line/1.0.7/leader-line.min.js"></script>

<script>
    const traceData = {json.dumps(trace_data)};
    const totalSteps = traceData.length - 1;
    let currentStep = 0;
    let lines = [];
    let isPlaying = false;
    let playInterval = null;
    let playSpeed = 1000;

    const els = {{
        frames: document.getElementById('frames-col'),
        objects: document.getElementById('objects-col'),
        timeline: document.getElementById('timeline'),
        stepLabel: document.getElementById('step-label'),
        codePanel: document.getElementById('code-panel'),
        playButton: document.getElementById('play-button'),
        playIcon: document.getElementById('play-icon'),
        playText: document.getElementById('play-text'),
        consoleOutput: document.getElementById('console-output'),
        consoleBody: document.getElementById('console-body'),
        currentLineInfo: document.getElementById('current-line-info'),
        sidebar: document.getElementById('sidebar'),
        sidebarToggleIcon: document.getElementById('sidebar-toggle-icon')
    }};
    
    els.timeline.max = totalSteps;

    // ==================== THEME MANAGEMENT ====================
    function toggleTheme() {{
        const body = document.body;
        const currentTheme = body.getAttribute('data-theme');
        const newTheme = currentTheme === 'light' ? 'dark' : 'light';
        body.setAttribute('data-theme', newTheme);
        
        const themeIcon = document.getElementById('theme-icon');
        const themeText = document.getElementById('theme-text');
        
        if (newTheme === 'dark') {{
            themeIcon.className = 'fas fa-sun';
            themeText.textContent = 'Light Mode';
        }} else {{
            themeIcon.className = 'fas fa-moon';
            themeText.textContent = 'Dark Mode';
        }}
        
        localStorage.setItem('theme', newTheme);
        
        // Redraw lines for new theme
        setTimeout(render, 100);
    }}

    // Load saved theme
    const savedTheme = localStorage.getItem('theme') || 'light';
    if (savedTheme === 'dark') {{
        document.body.setAttribute('data-theme', 'dark');
        document.getElementById('theme-icon').className = 'fas fa-sun';
        document.getElementById('theme-text').textContent = 'Light Mode';
    }}

    // ==================== SIDEBAR MANAGEMENT ====================
    function toggleSidebar() {{
        els.sidebar.classList.toggle('collapsed');
        const icon = els.sidebarToggleIcon;
        if (els.sidebar.classList.contains('collapsed')) {{
            icon.className = 'fas fa-chevron-right';
        }} else {{
            icon.className = 'fas fa-chevron-left';
        }}
    }}

    // ==================== DS INFO ====================
    function showDSInfo(dsType) {{
        const info = {{
            'array': 'Arrays store elements in contiguous memory. Great for random access but expensive for insertions/deletions.',
            'stack': 'Stack follows LIFO principle. Used in function calls, undo operations, and expression evaluation.',
            'queue': 'Queue follows FIFO principle. Used in BFS, task scheduling, and buffering.',
            'dict': 'Hash tables provide O(1) average lookup. Uses hashing to map keys to values.',
            'tree': 'Trees organize data hierarchically. BST provides O(log n) operations when balanced.',
            'heap': 'Heaps maintain partial ordering. Used in priority queues and heap sort.',
            'graph': 'Graphs model relationships. Used in networks, social connections, and pathfinding.',
            'sorting': 'Different sorting algorithms trade time vs space complexity. QuickSort is often fastest in practice.'
        }};
        
        alert(info[dsType] || 'Information not available');
    }}

    // ==================== CONSOLE MANAGEMENT ====================
    function toggleConsole() {{
        els.consoleOutput.classList.toggle('show');
    }}

    function updateConsole(output) {{
        if (output && output.trim()) {{
            els.consoleBody.textContent = output;
            if (!els.consoleOutput.classList.contains('show')) {{
                els.consoleOutput.classList.add('show');
            }}
        }}
    }}

    // ==================== VISUALIZATION ====================
    function clearLines() {{ 
        lines.forEach(l => l.remove()); 
        lines = []; 
    }}

    function renderObject(id, data, topY) {{
        const box = document.createElement('div');
        box.className = 'obj-box';
        box.id = `obj-${{id}}`;
        box.style.top = topY + 'px';
        box.style.left = '20px';
        
        if (data.ds_type) {{
            box.setAttribute('data-ds', data.ds_type);
        }}
        
        let content = '';
        
        // LIST/ARRAY
        if(data.type === 'list' || data.ds_type === 'array') {{
            try {{
                const items = eval(data.value);
                content = '<table class="list-table"><tr class="idx-row">';
                items.forEach((_, i) => content += `<td>${{i}}</td>`);
                content += '</tr><tr>';
                items.forEach(val => {{
                    const displayVal = typeof val === 'string' ? `"${{val}}"` : val;
                    content += `<td>${{displayVal}}</td>`;
                }});
                content += '</tr></table>';
            }} catch(e) {{ 
                content = data.value; 
            }}
        }}
        // DICT
        else if(data.type === 'dict' || data.ds_type === 'dict') {{
            try {{
                const obj = eval('(' + data.value + ')');
                content = '<table class="var-table" style="font-size: 0.75rem;">';
                for (const [k, v] of Object.entries(obj)) {{
                    content += `<tr><td class="v-name">${{k}}</td><td class="v-val">${{v}}</td></tr>`;
                }}
                content += '</table>';
            }} catch(e) {{
                content = data.value;
            }}
        }}
        // FUNCTION
        else if(data.type === 'function' || data.ds_type === 'function') {{
            content = `<div style="padding:5px; font-weight:600; text-align:center;">${{data.value}}</div>`;
        }}
        // SET
        else if(data.type === 'set' || data.ds_type === 'set') {{
            content = `<div style="padding:5px;">${{data.value}}</div>`;
        }}
        // OTHER
        else {{
            content = data.value;
        }}

        const typeLabel = data.ds_type || data.type;
        const lengthInfo = data.length !== undefined ? ` [len:${{data.length}}]` : '';
        
        box.innerHTML = `
            <div class="obj-header">
                <span>${{typeLabel}}${{lengthInfo}}</span>
                <span class="obj-type-badge">${{data.type}}</span>
            </div>
            <div class="obj-content">${{content}}</div>
        `;
        
        els.objects.appendChild(box);
        return box;
    }}

    function render() {{
        clearLines();
        const step = traceData[currentStep];

        // 1. CODE HIGHLIGHTING
        document.querySelectorAll('.arrow-container').forEach(el => el.innerHTML = '');
        document.querySelectorAll('.highlight-cur, .highlight-prev').forEach(el => {{
            el.classList.remove('highlight-cur', 'highlight-prev');
        }});

        // Current Line (Red Arrow)
        const lineEl = document.getElementById(`code-line-${{step.line}}`);
        if(lineEl) {{
            lineEl.classList.add('highlight-cur');
            lineEl.querySelector('.arrow-container').innerHTML = '<span class="arrow-red">➡</span>';
            lineEl.scrollIntoView({{behavior: 'smooth', block: 'center'}});
            els.currentLineInfo.textContent = `Line: ${{step.line}}`;
        }}
        
        // Previous Line (Green Arrow)
        if(currentStep > 0) {{
            const prevStep = traceData[currentStep - 1];
            const prevEl = document.getElementById(`code-line-${{prevStep.line}}`);
            if(prevEl && prevStep.line !== step.line) {{
                prevEl.classList.add('highlight-prev');
                prevEl.querySelector('.arrow-container').innerHTML = '<span class="arrow-green">➡</span>';
            }}
        }}

        // 2. FRAMES
        els.frames.innerHTML = '';
        
        const objMap = new Map();
        const connections = [];

        const createRow = (k, valData) => {{
            const tr = document.createElement('tr');
            let valHtml = '';
            
            if(valData.is_complex) {{
                const anchorId = `anchor-${{Math.random().toString(36).substr(2,9)}}`;
                valHtml = `<div id="${{anchorId}}" class="dot"></div>`;
                connections.push({{ startId: anchorId, endObjId: valData.id }});
                objMap.set(valData.id, valData);
            }} else {{
                valHtml = valData.value;
                if(valData.type === 'str') valHtml = `<span style="color: var(--success);">"${{valData.value}}"</span>`;
                else if(valData.type === 'int' || valData.type === 'float') {{
                    valHtml = `<span style="color: var(--accent);">${{valData.value}}</span>`;
                }}
                else if(valData.type === 'bool') {{
                    valHtml = `<span style="color: var(--warning);">${{valData.value}}</span>`;
                }}
            }}
            
            tr.innerHTML = `<td class="v-name">${{k}}</td><td class="v-val">${{valHtml}}</td>`;
            return tr;
        }};

        // A. GLOBAL FRAME
        const gFrame = document.createElement('div');
        gFrame.className = 'frame';
        
        if(step.globals && Object.keys(step.globals).length > 0) {{
            const table = document.createElement('table');
            table.className = 'var-table';
            for(const [k, v] of Object.entries(step.globals)) {{
                table.appendChild(createRow(k, v));
            }}
            gFrame.innerHTML = '<div class="frame-head"><i class="fas fa-globe frame-icon"></i>Global frame</div>';
            gFrame.appendChild(table);
            els.frames.appendChild(gFrame);
        }} else if(step.func === 'Global') {{
            const table = document.createElement('table');
            table.className = 'var-table';
            for(const [k, v] of Object.entries(step.locals)) {{
                table.appendChild(createRow(k, v));
            }}
            gFrame.innerHTML = '<div class="frame-head"><i class="fas fa-globe frame-icon"></i>Global frame</div>';
            gFrame.appendChild(table);
            els.frames.appendChild(gFrame);
        }}

        // B. CURRENT FUNCTION FRAME
        if(step.func !== 'Global') {{
            const fFrame = document.createElement('div');
            fFrame.className = 'frame';
            const table = document.createElement('table');
            table.className = 'var-table';
            
            for(const [k, v] of Object.entries(step.locals)) {{
                table.appendChild(createRow(k, v));
            }}
            
            // RETURN VALUE
            if(step.return_val) {{
                const tr = document.createElement('tr');
                tr.className = 'retval-row';
                
                let valHtml = step.return_val.value;
                if(step.return_val.is_complex) {{
                    const anchorId = `anchor-ret`;
                    valHtml = `<div id="${{anchorId}}" class="dot"></div>`;
                    connections.push({{ startId: anchorId, endObjId: step.return_val.id }});
                    objMap.set(step.return_val.id, step.return_val);
                }} else {{
                    if(step.return_val.type === 'int' || step.return_val.type === 'float') {{
                        valHtml = `<span style="color: var(--accent);">${{valHtml}}</span>`;
                    }}
                }}
                
                tr.innerHTML = `<td class="v-name retval-label"><i class="fas fa-undo"></i> Return<br>value</td><td class="v-val">${{valHtml}}</td>`;
                table.appendChild(tr);
            }}

            fFrame.innerHTML = `<div class="frame-head"><i class="fas fa-function frame-icon"></i>${{step.func}}()</div>`;
            fFrame.appendChild(table);
            els.frames.appendChild(fFrame);
        }}

        // 3. RENDER OBJECTS (HEAP)
        els.objects.innerHTML = '';
        let topY = 20;
        objMap.forEach((data, id) => {{
            const box = renderObject(id, data, topY);
            topY += box.offsetHeight + 30;
        }});

        // 4. DRAW CONNECTIONS
        setTimeout(() => {{
            connections.forEach(conn => {{
                const start = document.getElementById(conn.startId);
                const end = document.getElementById(`obj-${{conn.endObjId}}`);
                if(start && end) {{
                    const isDark = document.body.getAttribute('data-theme') === 'dark';
                    lines.push(new LeaderLine(start, end, {{
                        color: isDark ? '#8b8d94' : '#6c757d',
                        size: 2,
                        path: 'grid',
                        startSocket: 'right',
                        endSocket: 'left',
                        endPlug: 'arrow3'
                    }}));
                }}
            }});
        }}, 10);

        // 5. UPDATE CONSOLE
        if(step.stdout) {{
            updateConsole(step.stdout);
        }}

        // 6. FOOTER
        els.timeline.value = currentStep;
        els.stepLabel.innerHTML = `
            <i class="fas fa-stream"></i> 
            Step <strong>${{currentStep + 1}}</strong> of <strong>${{totalSteps + 1}}</strong>
        `;
    }}

    // ==================== PLAYBACK CONTROLS ====================
    function togglePlay() {{
        isPlaying = !isPlaying;
        
        if(isPlaying) {{
            els.playButton.classList.add('playing');
            els.playIcon.className = 'fas fa-pause';
            els.playText.textContent = 'Pause';
            play();
        }} else {{
            els.playButton.classList.remove('playing');
            els.playIcon.className = 'fas fa-play';
            els.playText.textContent = 'Play';
            if(playInterval) {{
                clearInterval(playInterval);
                playInterval = null;
            }}
        }}
    }}

    function play() {{
        if(playInterval) clearInterval(playInterval);
        
        playInterval = setInterval(() => {{
            if(currentStep < totalSteps) {{
                nextStep();
            }} else {{
                togglePlay();
            }}
        }}, playSpeed);
    }}

    function updateSpeed() {{
        const select = document.getElementById('speed-select');
        playSpeed = parseInt(select.value);
        
        if(isPlaying) {{
            play(); // Restart with new speed
        }}
    }}

    // ==================== NAVIGATION ====================
    function setStep(s) {{ 
        currentStep = Math.max(0, Math.min(s, totalSteps)); 
        render(); 
    }}
    
    function nextStep() {{ setStep(currentStep + 1); }}
    function prevStep() {{ setStep(currentStep - 1); }}
    function firstStep() {{ setStep(0); }}
    function lastStep() {{ setStep(totalSteps); }}
    
    els.timeline.oninput = (e) => {{
        setStep(parseInt(e.target.value));
    }};

    // Keyboard shortcuts
    document.addEventListener('keydown', (e) => {{
        if(e.key === 'ArrowRight') nextStep();
        else if(e.key === 'ArrowLeft') prevStep();
        else if(e.key === ' ') {{
            e.preventDefault();
            togglePlay();
        }}
        else if(e.key === 'Home') firstStep();
        else if(e.key === 'End') lastStep();
    }});
    
    window.addEventListener('resize', () => {{
        setTimeout(render, 100);
    }});
    
    // Initialize
    render();

</script>
</body>
</html>
    """
    
    with open(output_filename, "w", encoding="utf-8") as f:
        f.write(html_content)
    
    return output_filename