#!/usr/bin/env python3
"""
DAG (Directed Acyclic Graph) visualizer for Rhylthyme programs.

This module generates static D3.js visualizations showing the task dependency
structure of Rhylthyme programs as directed acyclic graphs.
"""

import os
import json
import argparse
import webbrowser
from pathlib import Path
from typing import Dict, Any, List, Set, Tuple

# Try local modules first (for Vercel deployment), fallback to rhylthyme package
try:
    from rhylthyme_web.rhylthyme import load_program_file, get_environment_icon
except ImportError:
    from rhylthyme.program_runner import load_program_file
    from rhylthyme.environment_icons import get_environment_icon

def extract_step_dependencies(program: Dict[str, Any]) -> Tuple[List[Dict], List[Dict]]:
    """
    Extract steps and their dependencies from a program.
    
    Args:
        program: The program dictionary
        
    Returns:
        Tuple of (nodes, edges) where:
        - nodes: List of step nodes with id, name, track info
        - edges: List of dependency edges with source and target step IDs
    """
    nodes = []
    edges = []
    step_to_track = {}
    
    # Create a virtual start node
    nodes.append({
        'id': 'program_start',
        'name': 'Program Start',
        'track': 'system',
        'type': 'start',
        'description': 'Program initialization'
    })
    
    # Extract all steps and track their relationships
    for track in program.get('tracks', []):
        track_id = track.get('trackId', 'unknown')
        track_name = track.get('name', 'Unknown Track')
        
        for step in track.get('steps', []):
            step_id = step.get('stepId')
            step_name = step.get('name', step_id)
            step_description = step.get('description', '')
            task = step.get('task', step.get('tasks', ['']))
            if isinstance(task, list):
                task = ', '.join(task) if task else ''
            
            # Extract duration information
            duration = step.get('duration', {})
            duration_info = None
            if duration:
                duration_type = duration.get('type')
                if duration_type == 'fixed':
                    seconds = duration.get('seconds', 0)
                    duration_info = f"{seconds}s"
                elif duration_type == 'variable':
                    min_sec = duration.get('minSeconds', 0)
                    max_sec = duration.get('maxSeconds', 0)
                    default_sec = duration.get('defaultSeconds', 0)
                    duration_info = f"{min_sec}-{max_sec}s (default: {default_sec}s)"
            
            # Extract buffer information
            pre_buffer = step.get('preBuffer', {})
            post_buffer = step.get('postBuffer', {})
            
            step_to_track[step_id] = track_id
            
            nodes.append({
                'id': step_id,
                'name': step_name,
                'track': track_id,
                'track_name': track_name,
                'type': 'step',
                'description': step_description,
                'task': task,
                'duration': duration_info,
                'preBuffer': pre_buffer,
                'postBuffer': post_buffer,
                'flex': step.get('flex', {'enabled': False})
            })
            
            # Analyze start trigger to create edges
            start_trigger = step.get('startTrigger', {})
            edges.extend(extract_dependencies_from_trigger(start_trigger, step_id))
    
    return nodes, edges

def extract_dependencies_from_trigger(trigger: Dict[str, Any], target_step: str) -> List[Dict]:
    """
    Extract dependency edges from a start trigger.
    
    Args:
        trigger: The start trigger configuration
        target_step: The step ID that this trigger applies to
        
    Returns:
        List of edge dictionaries with source and target
    """
    edges = []
    
    trigger_type = trigger.get('type')
    
    if trigger_type == 'programStart':
        edges.append({
            'source': 'program_start',
            'target': target_step,
            'type': 'programStart'
        })
    elif trigger_type == 'afterStep':
        source_step = trigger.get('stepId')
        if source_step:
            edges.append({
                'source': source_step,
                'target': target_step,
                'type': 'afterStep'
            })
    elif trigger_type == 'afterStepWithBuffer':
        source_step = trigger.get('stepId')
        buffer_seconds = trigger.get('bufferSeconds', 0)
        if source_step:
            edges.append({
                'source': source_step,
                'target': target_step,
                'type': 'afterStepWithBuffer',
                'buffer': buffer_seconds
            })
    elif trigger_type == 'programStartOffset':
        offset_seconds = trigger.get('offsetSeconds', 0)
        edges.append({
            'source': 'program_start',
            'target': target_step,
            'type': 'programStartOffset',
            'offset': offset_seconds
        })
    elif trigger_type == 'manual':
        # Manual triggers don't have dependencies from other steps
        pass
    elif trigger.get('logic'):  # Multiple triggers with logic
        logic_type = trigger.get('logic')
        triggers = trigger.get('triggers', [])
        for sub_trigger in triggers:
            edges.extend(extract_dependencies_from_trigger(sub_trigger, target_step))
    
    return edges

def parse_duration_string(duration_str) -> int:
    """Parse duration string like '30s', '2m', '1h' or integer into seconds."""
    if not duration_str:
        return 0
    
    # Handle integer input directly
    if isinstance(duration_str, int):
        return duration_str
    
    duration_str = str(duration_str).strip().lower()
    
    # Handle complex format like "90s)" by removing trailing characters
    if duration_str.endswith(')'):
        duration_str = duration_str[:-1]
    
    if duration_str.endswith('s'):
        return int(duration_str[:-1])
    elif duration_str.endswith('m'):
        return int(duration_str[:-1]) * 60
    elif duration_str.endswith('h'):
        return int(duration_str[:-1]) * 3600
    else:
        # Try to extract just the number part if no unit
        import re
        match = re.search(r'\d+', duration_str)
        if match:
            return int(match.group())
        return 0

def calculate_timeline_data(nodes: List[Dict], edges: List[Dict]) -> Dict:
    """
    Calculate timeline data for steps based on dependencies and durations.
    
    Args:
        nodes: List of step nodes
        edges: List of dependency edges
        
    Returns:
        Dictionary with timeline data including tracks, steps with calculated start times
    """
    # Build dependency graph
    step_dependencies = {}
    step_offsets = {}
    step_buffers = {}
    
    for edge in edges:
        target = edge['target']
        if target not in step_dependencies:
            step_dependencies[target] = []
        
        if edge['type'] == 'programStartOffset':
            step_offsets[target] = edge.get('offset', 0)
        elif edge['type'] == 'afterStepWithBuffer':
            step_buffers[(edge['source'], target)] = edge.get('buffer', 0)
        
        if edge['source'] != 'program_start':
            step_dependencies[target].append(edge['source'])
    
    # Calculate start times for each step
    step_start_times = {'program_start': 0}
    step_durations = {}
    step_buffers = {}
    
    # Extract durations and buffer information from nodes
    for node in nodes:
        if node['type'] == 'step':
            duration_str = node.get('duration', '0s')
            if duration_str:
                # Parse duration - handle both "60s" and "120-180s (default: 150s)" formats
                if 'default:' in duration_str:
                    # Variable duration, extract default
                    default_part = duration_str.split('default:')[1].strip()
                    duration = parse_duration_string(default_part)
                else:
                    # Fixed duration
                    duration = parse_duration_string(duration_str)
                step_durations[node['id']] = duration
            
            # Extract buffer durations
            pre_buffer = node.get('preBuffer', {})
            post_buffer = node.get('postBuffer', {})
            
            step_buffers[node['id']] = {
                'preBuffer': pre_buffer,
                'postBuffer': post_buffer
            }
    
    # Topological sort to calculate start times
    visited = set()
    temp_visited = set()
    
    def calculate_start_time(step_id):
        if step_id in temp_visited:
            return 0  # Cycle detected, return 0
        if step_id in visited:
            return step_start_times.get(step_id, 0)
        
        temp_visited.add(step_id)
        
        max_predecessor_end = 0
        
        # Handle program start offset
        if step_id in step_offsets:
            max_predecessor_end = step_offsets[step_id]
        
        # Handle dependencies
        for predecessor in step_dependencies.get(step_id, []):
            pred_start = calculate_start_time(predecessor)
            pred_duration = step_durations.get(predecessor, 0)
            pred_end = pred_start + pred_duration
            
            # Add buffer if exists
            buffer = step_buffers.get((predecessor, step_id), 0)
            pred_end += buffer
            
            max_predecessor_end = max(max_predecessor_end, pred_end)
        
        step_start_times[step_id] = max_predecessor_end
        temp_visited.remove(step_id)
        visited.add(step_id)
        
        return max_predecessor_end
    
    # Calculate start times for all steps
    for node in nodes:
        if node['type'] == 'step':
            calculate_start_time(node['id'])
    
    # Group steps by track
    tracks = {}
    for node in nodes:
        if node['type'] == 'step':
            track_id = node['track']
            if track_id not in tracks:
                tracks[track_id] = {
                    'trackId': track_id,
                    'name': node['track_name'],
                    'steps': []
                }
            
            start_time = step_start_times.get(node['id'], 0)
            duration = step_durations.get(node['id'], 0)
            
            # Get buffer information
            buffers = step_buffers.get(node['id'], {})
            pre_buffer = buffers.get('preBuffer', {})
            post_buffer = buffers.get('postBuffer', {})
            
            # Parse buffer durations
            pre_buffer_duration = parse_duration_string(pre_buffer.get('duration', ''))
            post_buffer_duration = parse_duration_string(post_buffer.get('duration', ''))
            
            # Calculate buffer start times
            pre_buffer_start = start_time - pre_buffer_duration if pre_buffer_duration > 0 else None
            post_buffer_start = start_time + duration if post_buffer_duration > 0 else None
            
            step_data = {
                'stepId': node['id'],
                'name': node['name'],
                'startTime': start_time,
                'duration': duration,
                'endTime': start_time + duration,
                'description': node.get('description', ''),
                'task': node.get('task', ''),
                'flex': node.get('flex', {'enabled': False})
            }
            
            # Add buffer information if present
            if pre_buffer_duration > 0:
                step_data['preBuffer'] = {
                    'startTime': pre_buffer_start,
                    'duration': pre_buffer_duration,
                    'endTime': start_time,
                    'description': pre_buffer.get('description', ''),
                    'tasks': pre_buffer.get('tasks', [])
                }
            
            if post_buffer_duration > 0:
                step_data['postBuffer'] = {
                    'startTime': post_buffer_start,
                    'duration': post_buffer_duration,
                    'endTime': post_buffer_start + post_buffer_duration,
                    'description': post_buffer.get('description', ''),
                    'tasks': post_buffer.get('tasks', [])
                }
            
            tracks[track_id]['steps'].append(step_data)
    
    # Calculate total timeline duration including all buffers
    max_end_time = 0
    min_start_time = 0
    
    for track_data in tracks.values():
        for step in track_data['steps']:
            # Check for pre-buffer extending timeline start
            if 'preBuffer' in step:
                min_start_time = min(min_start_time, step['preBuffer']['startTime'])
            
            # Check main step and post-buffer extending timeline end
            max_end_time = max(max_end_time, step['endTime'])
            if 'postBuffer' in step:
                max_end_time = max(max_end_time, step['postBuffer']['endTime'])
    
    # Adjust all times to start from 0 if there are negative pre-buffer times
    time_offset = -min_start_time if min_start_time < 0 else 0
    
    if time_offset > 0:
        for track_data in tracks.values():
            for step in track_data['steps']:
                step['startTime'] += time_offset
                step['endTime'] += time_offset
                if 'preBuffer' in step:
                    step['preBuffer']['startTime'] += time_offset
                    step['preBuffer']['endTime'] += time_offset
                if 'postBuffer' in step:
                    step['postBuffer']['startTime'] += time_offset
                    step['postBuffer']['endTime'] += time_offset
    
    total_duration = max_end_time + time_offset
    
    return {
        'tracks': list(tracks.values()),
        'totalDuration': total_duration,
        'timeScale': 'seconds'
    }

def generate_dag_html(nodes: List[Dict], edges: List[Dict], program_data: Dict[str, Any], environment_data: Dict[str, Any] = None, resource_constraints: List[Dict[str, Any]] = None) -> str:
    """
    Generate HTML with D3.js DAG visualization.
    
    Args:
        nodes: List of step nodes
        edges: List of dependency edges
        program_data: The full program data dictionary
        environment_data: The environment data dictionary (optional)
        resource_constraints: List of resource constraints (optional)
        
    Returns:
        HTML string with embedded D3.js visualization
    """
    
    # Extract program metadata
    program_name = program_data.get('name', 'Unknown Program')
    program_description = program_data.get('description', '')
    program_version = program_data.get('version', 'N/A')
    environment_type = program_data.get('environmentType', 'N/A')
    default_environment = program_data.get('defaultEnvironment', 'N/A')
    
    # Get environment icon
    environment_icon = get_environment_icon(environment_type) if environment_type != 'N/A' else 'fa-building'
    
    # Add environment information if available
    environment_name = 'N/A'
    environment_description = ''
    if environment_data:
        environment_name = environment_data.get('name', environment_data.get('environmentId', 'N/A'))
        environment_description = environment_data.get('description', '')
    elif program_data.get('environment'):
        environment_name = program_data.get('environment')
    
    # Convert data to JSON for embedding
    nodes_json = json.dumps(nodes, indent=2)
    edges_json = json.dumps(edges, indent=2)
    
    # Calculate timeline data
    timeline_data = calculate_timeline_data(nodes, edges)
    timeline_json = json.dumps(timeline_data, indent=2)
    
    # Prepare environment and resource data for JavaScript
    environment_json = json.dumps(environment_data or {}, indent=2)
    
    # Transform resource constraints to expected format for JavaScript
    transformed_constraints = []
    for constraint in (resource_constraints or []):
        # Handle both new format (name/capacity) and old format (task/maxConcurrent)
        if 'name' in constraint and 'capacity' in constraint:
            # New format - transform to JavaScript expected format
            transformed_constraints.append({
                'task': constraint['name'],
                'maxConcurrent': constraint['capacity'],
                'description': constraint.get('description', '')
            })
        elif 'task' in constraint and 'maxConcurrent' in constraint:
            # Old format - use as is
            transformed_constraints.append(constraint)
    
    resource_constraints_json = json.dumps(transformed_constraints, indent=2)
    
    # Calculate dynamic left margin based on longest track name
    max_track_name_length = 0
    if timeline_data.get('tracks'):
        for track in timeline_data['tracks']:
            track_name = track.get('name', '')
            max_track_name_length = max(max_track_name_length, len(track_name))
    
    # Estimate pixels needed for track names (approximately 8px per character + padding)
    estimated_track_label_width = max(120, max_track_name_length * 8 + 40)
    dynamic_left_margin = min(estimated_track_label_width, 300)  # Cap at 300px to avoid extreme widths
    
    # Format total duration for display
    total_duration = timeline_data.get('totalDuration', 0)
    
    def format_duration(seconds):
        if seconds >= 3600:
            hours = seconds / 3600
            return f"{hours:.1f} hours" if hours != int(hours) else f"{int(hours)} hours"
        elif seconds >= 60:
            minutes = seconds / 60
            return f"{minutes:.1f} minutes" if minutes != int(minutes) else f"{int(minutes)} minutes"
        else:
            return f"{int(seconds)} seconds"
    
    formatted_duration = format_duration(total_duration)
    
    # Check for program fixed duration
    program_duration_text = ""
    if program_data.get('duration'):
        duration_config = program_data['duration']
        if duration_config.get('type') == 'fixed':
            if duration_config.get('seconds'):
                program_duration_seconds = duration_config['seconds']
                program_duration_text = format_duration(program_duration_seconds)
            elif duration_config.get('timeString'):
                program_duration_text = duration_config['timeString']
    
    # Generate relevant preset buttons based on total duration
    preset_buttons = []
    duration_minutes = total_duration / 60
    
    # Define all possible presets
    all_presets = [2, 5, 15, 30, 60, 120, 240, 480]

    preset_buttons_html = '\n                    '.join(preset_buttons)
    
    # Build the HTML template using string formatting to avoid f-string conflicts with JavaScript
    html_template = """<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DAG Visualization - """ + program_name + """</title>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        'dag-blue': '#4B46B9',
                        'dag-green': '#28a745',
                        'dag-purple': '#6f42c1',
                        'dag-gray': '#6c757d'
                    }
                }
            }
        }
    </script>"""
    
    html_template += """
    <style>
        /* Custom styles for D3 elements that can't use Tailwind classes */
        .node {
            cursor: pointer;
        }
        
        .node.start {
            fill: #2C3E50;
            stroke: #34495E;
            stroke-width: 2px;
        }
        
        .node.step {
            stroke: #ffffff;
            stroke-width: 2px;
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
        }
        
        .node:hover {
            stroke: #2C3E50;
            stroke-width: 3px;
            filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));
        }
        
        .node.dragging {
            stroke: #e74c3c;
            stroke-width: 4px;
            filter: drop-shadow(0 6px 12px rgba(0,0,0,0.3));
        }
        
        /* Execution state styles */
        .node.active {
            animation: pulse 1.5s infinite;
            stroke: #f39c12;
            stroke-width: 4px;
        }
        
        .node.completed {
            opacity: 0.4;
            filter: grayscale(50%);
        }
        
        @keyframes pulse {
            0% {
                stroke-width: 4px;
                stroke-opacity: 1;
            }
            50% {
                stroke-width: 6px;
                stroke-opacity: 0.7;
            }
            100% {
                stroke-width: 4px;
                stroke-opacity: 1;
            }
        }
        
        /* Timeline execution indicators */
        .current-time-line {
            stroke: #e74c3c;
            stroke-width: 3px;
            stroke-dasharray: 5,5;
            animation: dashMove 1s linear infinite;
        }
        
        @keyframes dashMove {
            0% {
                stroke-dashoffset: 0;
            }
            100% {
                stroke-dashoffset: 10;
            }
        }
        
        .task-bar.active {
            stroke: #f39c12;
            stroke-width: 3px;
            animation: taskPulse 1.5s infinite;
        }
        
        .task-bar.completed {
            opacity: 0.4;
            filter: grayscale(50%);
        }
        
        @keyframes taskPulse {
            0% {
                stroke-width: 3px;
            }
            50% {
                stroke-width: 5px;
            }
            100% {
                stroke-width: 3px;
            }
        }
        
        /* Itinerary execution styles */
        .timeline-item.active {
            background-color: #fff3cd;
            border-color: #f39c12;
            animation: highlightPulse 2s infinite;
        }
        
        .timeline-item.completed {
            opacity: 0.5;
            filter: grayscale(30%);
        }
        
        @keyframes highlightPulse {
            0% {
                background-color: #fff3cd;
            }
            50% {
                background-color: #ffeaa7;
            }
            100% {
                background-color: #fff3cd;
            }
        }
        
        #dag-svg {
            cursor: grab;
        }
        
        #dag-svg.panning {
            cursor: grabbing;
        }
        
        /* Dark mode styles */
        .dark-mode {
            background-color: #1a1a1a;
            color: #e5e7eb;
        }
        
        .dark-mode .bg-white {
            background-color: #2d3748 !important;
            color: #e5e7eb !important;
        }
        
        .dark-mode .bg-gray-50 {
            background-color: #374151 !important;
            color: #e5e7eb !important;
        }
        
        .dark-mode .bg-gray-100 {
            background-color: #4a5568 !important;
            color: #e5e7eb !important;
        }
        
        .dark-mode .text-gray-800 {
            color: #e5e7eb !important;
        }
        
        .dark-mode .text-gray-600 {
            color: #cbd5e0 !important;
        }
        
        .dark-mode .text-gray-500 {
            color: #a0aec0 !important;
        }
        
        .dark-mode .border-gray-200 {
            border-color: #4a5568 !important;
        }
        
        .dark-mode .border-gray-300 {
            border-color: #4a5568 !important;
        }
        
        .dark-mode .track-lane {
            fill: #374151;
            stroke: #4a5568;
        }
        
        .dark-mode .track-label {
            fill: #e5e7eb;
        }
        
        .dark-mode .time-label {
            fill: #e5e7eb;
        }
        
        .dark-mode .time-axis {
            stroke: #e5e7eb;
        }
        
        .dark-mode .time-tick {
            stroke: #9ca3af;
        }
        
        .dark-mode .node.start {
            fill: #4a5568;
            stroke: #6b7280;
        }
        
        .dark-mode .link {
            stroke: #9ca3af;
        }
        
        .dark-mode #settings-modal .bg-white {
            background-color: #2d3748 !important;
        }
        
        .dark-mode #settings-modal .text-gray-800 {
            color: #e5e7eb !important;
        }
        
        .dark-mode #settings-modal .text-gray-700 {
            color: #cbd5e0 !important;
        }
        
        .dark-mode #settings-modal .text-gray-600 {
            color: #a0aec0 !important;
        }
        
        .dark-mode #settings-modal .border-gray-200 {
            border-color: #4a5568 !important;
        }
        
        .dark-mode #settings-modal .border-gray-300 {
            border-color: #4a5568 !important;
        }
        
        .dark-mode #settings-modal input,
        .dark-mode #settings-modal select {
            background-color: #374151;
            border-color: #4a5568;
            color: #e5e7eb;
        }
        
        .link {
            fill: none;
            stroke: #7F8C8D;
            stroke-width: 2px;
            marker-end: url(#arrowhead);
            opacity: 0.8;
        }
        
        .link.afterStep {
            stroke: #3498DB;
        }
        
        .link.afterStepWithBuffer {
            stroke: #F39C12;
            stroke-dasharray: 5,5;
        }
        
        .link.programStart {
            stroke: #27AE60;
        }
        
        .link.programStartOffset {
            stroke: #9B59B6;
            stroke-dasharray: 3,3;
        }
        
        .node-label {
            text-anchor: middle;
            dominant-baseline: central;
            fill: white;
            font-size: 12px;
            font-weight: bold;
            pointer-events: none;
        }
        
        .tooltip {
            position: absolute;
            padding: 10px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            border-radius: 4px;
            pointer-events: none;
            font-size: 12px;
            max-width: 200px;
            z-index: 1000;
        }
        
        .edge-label {
            text-anchor: middle;
            dominant-baseline: central;
            fill: #333;
            font-size: 10px;
            font-weight: bold;
            background: white;
            padding: 2px 4px;
            border-radius: 3px;
            pointer-events: none;
        }
        
        .edge-label-bg {
            fill: white;
            stroke: #ccc;
            stroke-width: 1px;
            rx: 3;
            ry: 3;
        }
        
        /* Timeline view styles */
        .track-lane {
            fill: #f8f9fa;
            stroke: #dee2e6;
            stroke-width: 1px;
        }
        
        .track-label {
            fill: #333;
            font-size: 12px;
            font-weight: bold;
            text-anchor: end;
            dominant-baseline: central;
        }
        
        .task-bar {
            stroke: #ffffff;
            stroke-width: 1px;
            rx: 4;
            ry: 4;
            cursor: pointer;
            filter: drop-shadow(0 1px 3px rgba(0,0,0,0.1));
            transition: all 0.2s ease;
        }
        
        .task-bar:hover {
            stroke: #2C3E50;
            stroke-width: 2px;
            filter: drop-shadow(0 3px 6px rgba(0,0,0,0.2));
            transform: translateY(-1px);
        }
        
        .task-label {
            fill: white;
            font-size: 10px;
            font-weight: bold;
            text-anchor: middle;
            dominant-baseline: central;
            pointer-events: none;
        }
        
        .time-axis {
            stroke: #333;
            stroke-width: 1px;
        }
        
        .time-tick {
            stroke: #666;
            stroke-width: 1px;
        }
        
        .time-label {
            fill: #333;
            font-size: 10px;
            text-anchor: middle;
            dominant-baseline: hanging;
        }
    </style>
</head>
<body class="bg-gray-100 font-sans">
    <div class="max-w-7xl mx-auto bg-white rounded-lg shadow-lg p-6">"""
    
    html_template += f"""
        <h1 class="text-3xl font-bold text-gray-800 mb-2 pb-2 border-b-2 flex items-center gap-3" style="border-color: #4B46B9;">
            <i class="fas {environment_icon} text-gray-600" title="Environment Type: {environment_type}"></i>
            {program_name}
        </h1>
        
        <div class="text-dag-gray italic mb-8 leading-relaxed text-lg">
            {program_description}
        </div>
        
        <div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 text-sm">
            <div class="flex flex-col items-center text-center">
                <span class="font-bold text-gray-600 uppercase text-xs tracking-wide mb-1">{"Program Duration" if program_duration_text else "Estimated Total Time"}</span>
                <div class="flex flex-col items-center">
                    {f'<span class="text-blue-600 font-semibold text-sm mb-1">{program_duration_text}</span>' if program_duration_text else ''}
                    <span class="text-dag-green font-semibold text-sm">{f"Est: {formatted_duration}" if program_duration_text else formatted_duration}</span>
                </div>
            </div>
            <div class="flex flex-col items-center text-center">
                <span class="font-bold text-gray-600 uppercase text-xs tracking-wide mb-1">Version</span>
                <span class="font-semibold text-sm" style="color: #4B46B9;">{program_version}</span>
            </div>
            <div class="flex flex-col items-center text-center">
                <span class="font-bold text-gray-600 uppercase text-xs tracking-wide mb-1">Environment Type</span>
                <span class="text-dag-purple font-semibold text-sm">{environment_type}</span>
            </div>
            <div class="flex flex-col items-center text-center">
                <span class="font-bold text-gray-600 uppercase text-xs tracking-wide mb-1">Environment</span>
                <span class="text-dag-purple font-semibold text-sm" title="{environment_description}">{environment_name}</span>
            </div>
            <div class="flex flex-col items-center text-center">
                <span class="font-bold text-gray-600 uppercase text-xs tracking-wide mb-1">Resource Constraints</span>
                <span class="text-dag-gray font-semibold text-sm">{len(resource_constraints or [])}</span>
            </div>
        </div>
        
        <!-- Execution Controls -->
        <div class="border border-gray-300 rounded-lg p-4 mb-8 bg-gray-50">
            <div class="flex items-center justify-between">
                <div class="flex items-center gap-4">
                    <h3 class="text-lg font-semibold text-gray-800">Program Execution</h3>
                    <div class="flex items-center gap-2">
                        <button id="start-btn" class="bg-gray-200 hover:bg-gray-300 text-green-600 font-bold py-2 px-4 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed border border-gray-300">
                            <i class="fas fa-play mr-2"></i>Start
                        </button>
                        <button id="pause-btn" class="bg-gray-200 hover:bg-gray-300 text-yellow-600 font-bold py-2 px-4 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed border border-gray-300" disabled>
                            <i class="fas fa-pause mr-2"></i>Pause
                        </button>
                        <button id="stop-btn" class="bg-gray-200 hover:bg-gray-300 text-red-600 font-bold py-2 px-4 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed border border-gray-300" disabled>
                            <i class="fas fa-stop mr-2"></i>Stop
                        </button>
                    </div>
                </div>
                <div class="flex items-center gap-4">
                    
                    <div class="text-right">
                        <div class="text-sm text-gray-600">Current Time</div>
                        <div class="text-lg font-bold text-gray-800" id="current-time-display">00:00</div>
                    </div>
                    <div class="text-right">
                        <div class="text-sm text-gray-600">Status</div>
                        <div class="text-lg font-bold text-gray-800" id="execution-status">Stopped</div>
                    </div>
                    <div class="text-right">
                        <div class="text-sm text-gray-600">Speed</div>
                        <select id="speed-control" class="bg-white border border-gray-300 rounded px-2 py-1 text-gray-800 text-sm">
                            <option value="0.05">0.05x</option>
                            <option value="0.1">0.1x</option>
                            <option value="0.25">0.25x</option>
                            <option value="0.5">0.5x</option>
                            <option value="1" selected>1x</option>
                            <option value="2">2x</option>
                            <option value="5">5x</option>
                            <option value="10">10x</option>
                            <option value="20">20x</option>
                            <option value="50">50x</option>
                        </select>
                    </div>

                </div>
                                    <button id="settings-btn" class="bg-gray-500 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded transition-colors" title="Settings">
                        <i class="fas fa-cog mr-2"></i>Settings
                    </button>
            </div>
        </div>
        
        <!-- Settings Modal -->
        <div id="settings-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
            <div class="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
                <div class="p-6">
                    <div class="flex items-center justify-between mb-6">
                        <h2 class="text-2xl font-bold text-gray-800">Visualization Settings</h2>
                        <button id="close-settings" class="text-gray-500 hover:text-gray-700 text-2xl">&times;</button>
                    </div>
                    
                    <!-- Theme Settings -->
                    <div class="mb-6">
                        <h3 class="text-lg font-semibold text-gray-700 mb-3">Theme</h3>
                        <div class="flex items-center gap-4">
                            <label class="flex items-center">
                                <input type="radio" name="theme" value="light" class="mr-2" checked>
                                <i class="fas fa-sun mr-2 text-yellow-500"></i>
                                Light Mode
                            </label>
                            <label class="flex items-center">
                                <input type="radio" name="theme" value="dark" class="mr-2">
                                <i class="fas fa-moon mr-2 text-blue-500"></i>
                                Dark Mode
                            </label>
                        </div>
                    </div>
                    
                    <!-- Default Views -->
                    <div class="mb-6">
                        <h3 class="text-lg font-semibold text-gray-700 mb-3">Default Views</h3>
                        <div class="grid grid-cols-2 gap-3">
                            <label class="flex items-center">
                                <input type="checkbox" id="default-dag" class="mr-2">
                                <i class="fas fa-project-diagram mr-2"></i>
                                DAG View
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" id="default-timeline" class="mr-2" checked>
                                <i class="fas fa-chart-gantt mr-2"></i>
                                Timeline View
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" id="default-resources" class="mr-2">
                                <i class="fas fa-microchip mr-2"></i>
                                Resources View
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" id="default-itinerary" class="mr-2">
                                <i class="fas fa-list-ul mr-2"></i>
                                Itinerary View
                            </label>
                        </div>
                    </div>
                    
                    <!-- Default Time Scale -->
                    <div class="mb-6">
                        <h3 class="text-lg font-semibold text-gray-700 mb-3">Default Time Format</h3>
                        <select id="default-time-format" class="w-full px-3 py-2 border border-gray-300 rounded-md">
                            <option value="relative-seconds">Relative (seconds)</option>
                            <option value="relative-minutes" selected>Relative (minutes)</option>
                            <option value="relative-hours">Relative (hours)</option>
                            <option value="time-of-day-12h">Time of Day (12hr)</option>
                            <option value="time-of-day-24h">Time of Day (24hr)</option>
                        </select>
                    </div>
                    
                    <!-- Time Presets -->
                    <div class="mb-6">
                        <h3 class="text-lg font-semibold text-gray-700 mb-3">Time Presets</h3>
                        <div class="grid grid-cols-4 gap-3">
                            <label class="flex items-center">
                                <input type="checkbox" class="preset-checkbox mr-2" data-minutes="2">
                                2 min
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" class="preset-checkbox mr-2" data-minutes="5">
                                5 min
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" class="preset-checkbox mr-2" data-minutes="15">
                                15 min
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" class="preset-checkbox mr-2" data-minutes="30">
                                30 min
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" class="preset-checkbox mr-2" data-minutes="60">
                                1 hr
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" class="preset-checkbox mr-2" data-minutes="120">
                                2 hr
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" class="preset-checkbox mr-2" data-minutes="240">
                                4 hr
                            </label>
                            <label class="flex items-center">
                                <input type="checkbox" class="preset-checkbox mr-2" data-minutes="480">
                                8 hr
                            </label>
                        </div>
                        <div class="mt-3 text-sm text-gray-600">
                            <i class="fas fa-info-circle mr-1"></i>
                            Selected presets will be displayed directly in the timeline controls. If no presets are selected, the preset buttons will be hidden.
                        </div>
                    </div>
                    
                    <!-- Actions -->
                    <div class="flex items-center justify-between pt-4 border-t border-gray-200">
                        <button id="reset-settings" class="px-4 py-2 text-gray-600 hover:text-gray-800 border border-gray-300 rounded">
                            Reset to Defaults
                        </button>
                        <div class="flex gap-3">
                            <button id="cancel-settings" class="px-4 py-2 text-gray-600 hover:text-gray-800">
                                Cancel
                            </button>
                            <button id="save-settings" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded">
                                Save Settings
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        
        <div class="flex flex-col lg:flex-row gap-6 mb-6 relative" id="panels-container">
            <!-- Floating View Toggle Buttons -->
            <div class="absolute bottom-0 left-0 z-10 bg-white rounded-lg border border-gray-200 shadow-lg">
                <div class="inline-flex rounded-lg border border-gray-200 bg-gray-50 p-1">
                    <button class="view-toggle px-4 py-2 text-sm font-medium rounded-md transition-colors text-gray-700 hover:text-gray-900" data-view="dag" id="view-dag">
                        <i class="fas fa-project-diagram"></i>
                    </button>
                    <button class="view-toggle px-4 py-2 text-sm font-medium rounded-md transition-colors text-gray-700 hover:text-gray-900" data-view="timeline" id="view-timeline">
                        <i class="fas fa-chart-gantt"></i>
                    </button>
                    <button class="view-toggle px-4 py-2 text-sm font-medium rounded-md transition-colors text-gray-700 hover:text-gray-900 relative" data-view="resources" id="view-resources">
                        <i class="fas fa-microchip"></i>
                        <span class="resource-warning-badge absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center hidden"></span>
                    </button>
                    <button class="view-toggle px-4 py-2 text-sm font-medium rounded-md transition-colors text-gray-700 hover:text-gray-900" data-view="itinerary" id="view-itinerary">
                        <i class="fas fa-list-ul"></i>
                    </button>
                </div>
            </div>
            
            <div class="flex-1 min-w-0" id="dag-panel">
                <div class="flex items-center justify-center gap-3 mb-3 p-3 bg-gray-50 rounded border border-gray-200 h-20">
                    <div class="flex flex-col items-center gap-1">
                        <button class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-1.5 px-3 rounded text-sm transition-colors" id="dag-zoom-in">➕</button>
                        <button class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-1.5 px-3 rounded text-sm transition-colors" id="dag-zoom-out">➖</button>
                    </div>
                    <div class="text-xs text-gray-600 font-bold min-w-24 text-center" id="dag-zoom-info">🔍 Zoom: 1.0x</div>
                    <button class="bg-teal-500 hover:bg-teal-600 text-white font-bold py-1.5 px-3 rounded text-xs transition-colors" id="dag-zoom-reset">Reset View</button>
                </div>
                <div class="w-full h-96 border border-gray-300 rounded overflow-hidden">
                    <svg id="dag-svg" width="100%" height="100%"></svg>
                </div>
            </div>
            
            <div class="flex-1 min-w-0" id="timeline-panel">
                <div class="flex items-center justify-between gap-4 mb-3 p-3 bg-gray-50 rounded border border-gray-200 h-20">
                    <div class="flex items-center gap-2">
                        <div class="flex flex-col items-center gap-1">
                            <button class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-1.5 px-3 rounded text-xs transition-colors" id="zoom-in">➕</button>
                            <button class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-1.5 px-3 rounded text-xs transition-colors" id="zoom-out">➖</button>
                        </div>
                        <div class="text-xs text-gray-600 font-bold min-w-20 text-center" id="scale-info">🔍 Scale: 1.0x</div>
                    </div>
                    <div class="flex items-center gap-2 flex-wrap" id="preset-buttons-container" style="display: none;">
                        {preset_buttons_html}
                    </div>
                    <div class="flex items-center gap-2">
                        <div class="flex items-center gap-1 border-r border-gray-300 pr-2">
                            <select id="auto-plan-strategy" class="text-xs text-gray-600 font-bold bg-blue-50 border border-blue-300 rounded px-2 py-1">
                                <option value="cover">Event Cover</option>
                                <option value="synchronized_finish">Sync Finish</option>
                                <option value="minimize_length">Min Length</option>
                            </select>
                            <button id="auto-plan-btn" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-1 px-2 rounded text-xs transition-colors" title="Auto-plan timeline">
                                <i class="fas fa-magic mr-1"></i>Plan
                            </button>
                            <button id="reset-plan-btn" class="bg-gray-300 hover:bg-gray-400 text-gray-700 font-bold py-1 px-2 rounded text-xs transition-colors" title="Reset to original timeline">
                                <i class="fas fa-undo mr-1"></i>Reset
                            </button>
                        </div>
                        <select id="time-format-selector" class="text-xs text-gray-600 font-bold bg-gray-200 border border-gray-300 rounded px-2 py-1">
                            <option value="relative-seconds">Relative (seconds)</option>
                            <option value="relative-minutes" selected>Relative (minutes)</option>
                            <option value="relative-hours">Relative (hours)</option>
                            <option value="time-of-day-12h">Time of Day (12hr)</option>
                            <option value="time-of-day-24h">Time of Day (24hr)</option>
                        </select>
                        <input type="time" id="start-time-picker" class="text-xs bg-gray-200 border border-gray-300 rounded px-2 py-1" value="09:00" title="Set program start time" style="display: none;">
                    </div>
                </div>
                <div class="w-full h-96 border border-gray-300 rounded overflow-x-auto overflow-y-hidden relative">
                    <svg id="timeline-svg" width="100%" height="100%"></svg>
                    <div class="absolute top-2 right-2 bg-black bg-opacity-70 text-white py-1 px-2 rounded text-xs pointer-events-none opacity-0 transition-opacity duration-300 hover:opacity-100" id="scroll-hint">← Scroll horizontally →</div>
                </div>
            </div>
            
            <div class="flex-1 min-w-0" id="resources-panel" style="display: none;">
                <div class="bg-white border border-gray-200 rounded p-6 overflow-y-auto" style="height: 476px;">
                    <h3 class="text-lg font-semibold text-gray-800 mb-4">Resource Usage Over Time</h3>
                    <div id="resources-content">
                        <!-- Resource usage content will be generated by JavaScript -->
                    </div>
                </div>
            </div>
            
            <div class="flex-1 min-w-0" id="itinerary-panel" style="display: none;">
                <div class="bg-white border border-gray-200 rounded p-6 overflow-y-auto" style="height: 476px;">
                    <div id="itinerary-content">
                        <!-- Itinerary content will be generated by JavaScript -->
                    </div>
                </div>
            </div>
        </div>
        
        <div class="bg-gray-50 p-4 rounded border border-gray-200" id="legend">
            <strong class="text-gray-800">Tracks:</strong>
            <div id="legend-items" class="mt-2"></div>
        </div>
    </div>"""
    
    html_template += """
    <script>
        // Data
        const nodes = """ + nodes_json + """;
        const edges = """ + edges_json + """;
        const timelineData = """ + timeline_json + """;
        const environmentData = """ + environment_json + """;
        const resourceConstraints = """ + resource_constraints_json + """;
        
        // Settings Management
        const defaultSettings = {
            theme: 'light',
            defaultViews: ['timeline'],
            defaultTimeFormat: 'relative-minutes',
            enabledPresets: []
        };
        
        let userSettings = { ...defaultSettings };
        
        // Cookie utilities
        function setCookie(name, value, days = 30) {
            const expires = new Date();
            expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
            document.cookie = name + '=' + JSON.stringify(value) + ';expires=' + expires.toUTCString() + ';path=/';
        }
        
        function getCookie(name) {
            const nameEQ = name + '=';
            const ca = document.cookie.split(';');
            for (let i = 0; i < ca.length; i++) {
                let c = ca[i];
                while (c.charAt(0) === ' ') c = c.substring(1, c.length);
                if (c.indexOf(nameEQ) === 0) {
                    try {
                        return JSON.parse(c.substring(nameEQ.length, c.length));
                    } catch (e) {
                        return null;
                    }
                }
            }
            return null;
        }
        
        // Load user settings from cookies
        function loadUserSettings() {
            const savedSettings = getCookie('rhylthyme_settings');
            if (savedSettings) {
                userSettings = { ...defaultSettings, ...savedSettings };
            }
            applyUserSettings();
        }
        
        // Apply user settings to the interface
        function applyUserSettings() {
            // Apply theme
            applyTheme(userSettings.theme);
            
            // Set default time format
            timeFormat = userSettings.defaultTimeFormat;
            document.getElementById('time-format-selector').value = userSettings.defaultTimeFormat;
            
            // Set default views
            activeViews = new Set(userSettings.defaultViews);
            
            // Apply preset visibility - show directly if presets are enabled, hide entirely if none
            const presetsContainer = document.getElementById('preset-buttons-container');
            if (userSettings.enabledPresets && userSettings.enabledPresets.length > 0) {
                presetsContainer.style.display = 'flex';
            } else {
                presetsContainer.style.display = 'none';
            }
            
            // Generate and apply custom presets
            generateCustomPresets();
            
            // Update view toggles and display
            updateView();
        }
        
        // Apply theme
        function applyTheme(theme) {
            const body = document.body;
            if (theme === 'dark') {
                body.classList.add('dark-mode');
            } else {
                body.classList.remove('dark-mode');
            }
        }
        
        // Generate custom preset buttons based on user settings
        function generateCustomPresets() {
            const container = document.getElementById('preset-buttons-container');
            const existingPresets = container.querySelectorAll('.time-preset');
            existingPresets.forEach(preset => preset.remove());
            
            // Create new presets based on user settings
            userSettings.enabledPresets.forEach(minutes => {
                const button = document.createElement('button');
                button.className = 'bg-dag-green hover:bg-green-600 text-white font-bold py-1 px-2 rounded text-xs mx-0.5 transition-colors time-preset';
                button.dataset.minutes = minutes;
                
                // Format button text
                if (minutes >= 60) {
                    const hours = minutes / 60;
                    button.textContent = hours === Math.floor(hours) ? `${Math.floor(hours)}hr` : `${hours}hr`;
                } else {
                    button.textContent = `${minutes}min`;
                }
                
                // Add click event
                button.addEventListener('click', function() {
                    const minutes = parseInt(this.dataset.minutes);
                    setTimelinePreset(minutes);
                });
                
                container.appendChild(button);
            });
        }
        
        // Settings Modal Management
        function openSettingsModal() {
            const modal = document.getElementById('settings-modal');
            modal.classList.remove('hidden');
            
            // Populate current settings
            populateSettingsModal();
        }
        
        function closeSettingsModal() {
            const modal = document.getElementById('settings-modal');
            modal.classList.add('hidden');
        }
        
        function populateSettingsModal() {
            // Set theme
            document.querySelector(`input[name="theme"][value="${userSettings.theme}"]`).checked = true;
            
            // Set default views
            document.getElementById('default-dag').checked = userSettings.defaultViews.includes('dag');
            document.getElementById('default-timeline').checked = userSettings.defaultViews.includes('timeline');
            document.getElementById('default-resources').checked = userSettings.defaultViews.includes('resources');
            document.getElementById('default-itinerary').checked = userSettings.defaultViews.includes('itinerary');
            
            // Set default time format
            document.getElementById('default-time-format').value = userSettings.defaultTimeFormat;
            
            // Set preset checkboxes
            document.querySelectorAll('.preset-checkbox').forEach(checkbox => {
                const minutes = parseInt(checkbox.dataset.minutes);
                checkbox.checked = userSettings.enabledPresets.includes(minutes);
            });
        }
        
        function saveSettings() {
            // Get theme
            const selectedTheme = document.querySelector('input[name="theme"]:checked').value;
            
            // Get default views
            const selectedViews = [];
            if (document.getElementById('default-dag').checked) selectedViews.push('dag');
            if (document.getElementById('default-timeline').checked) selectedViews.push('timeline');
            if (document.getElementById('default-resources').checked) selectedViews.push('resources');
            if (document.getElementById('default-itinerary').checked) selectedViews.push('itinerary');
            
            // Get default time format
            const selectedTimeFormat = document.getElementById('default-time-format').value;
            
            // Get enabled presets
            const selectedPresets = [];
            document.querySelectorAll('.preset-checkbox:checked').forEach(checkbox => {
                selectedPresets.push(parseInt(checkbox.dataset.minutes));
            });
            
            // Update user settings
            userSettings = {
                theme: selectedTheme,
                defaultViews: selectedViews.length > 0 ? selectedViews : ['timeline'], // Ensure at least one view
                defaultTimeFormat: selectedTimeFormat,
                enabledPresets: selectedPresets // Allow empty array - will hide presets
            };
            
            // Save to cookie
            setCookie('rhylthyme_settings', userSettings);
            
            // Apply settings immediately
            applyUserSettings();
            
            // Close modal
            closeSettingsModal();
            
            // Show confirmation
            showNotification('Settings saved successfully!', 'success');
        }
        
        function resetSettings() {
            userSettings = { ...defaultSettings };
            populateSettingsModal();
        }
        
        // Show notification
        function showNotification(message, type = 'info') {
            const notification = document.createElement('div');
            notification.className = `fixed top-4 right-4 z-50 px-4 py-2 rounded shadow-lg text-white transition-opacity duration-300 ${
                type === 'success' ? 'bg-green-500' : 
                type === 'error' ? 'bg-red-500' : 'bg-blue-500'
            }`;
            notification.textContent = message;
            
            document.body.appendChild(notification);
            
            setTimeout(() => {
                notification.style.opacity = '0';
                setTimeout(() => {
                    document.body.removeChild(notification);
                }, 300);
            }, 3000);
        }
        
        // Event listeners for settings
        document.getElementById('settings-btn').addEventListener('click', openSettingsModal);
        document.getElementById('close-settings').addEventListener('click', closeSettingsModal);
        document.getElementById('cancel-settings').addEventListener('click', closeSettingsModal);
        document.getElementById('save-settings').addEventListener('click', saveSettings);
        document.getElementById('reset-settings').addEventListener('click', resetSettings);
        
        // Auto-plan functionality
        async function autoPlan() {
            const strategy = document.getElementById('auto-plan-strategy').value;
            
            try {
                showToast('Generating auto-plan...', 'info');
                
                // Store original timeline data for reset functionality (only if not already stored)
                if (!originalTimelineData) {
                    originalTimelineData = JSON.parse(JSON.stringify(timelineData));
                }
                
                // Create a simplified program structure for the auto-planner
                const programData = {
                    tracks: timelineData.tracks.map(track => ({
                        trackId: track.trackId,
                        name: track.name,
                        priority: track.priority || 100,
                        steps: track.steps.map(step => ({
                            stepId: step.stepId,
                            name: step.name,
                            priority: step.priority || 100,
                            duration: {
                                type: 'fixed',
                                seconds: step.duration
                            },
                            startTrigger: {
                                type: 'programStart'  // Simplified for now
                            },
                            task: step.task || '',
                            flex: step.flex || { enabled: false }
                        }))
                    }))
                };
                
                // Apply auto-planning logic
                const result = await applyAutoPlanStrategy(programData, strategy);
                
                if (result.success) {
                    // Update timeline data with new timings
                    updateTimelineWithPlan(result.plan);
                    
                    // Refresh all views
                    refreshAllViews();
                    
                    showToast(`Auto-plan applied: ${strategy.replace('_', ' ')}`, 'success');
                } else {
                    showToast('Auto-plan failed: ' + (result.error || 'Unknown error'), 'error');
                }
                
            } catch (error) {
                console.error('Auto-plan error:', error);
                showToast('Auto-plan failed: ' + error.message, 'error');
            }
        }
        
        async function applyAutoPlanStrategy(programData, strategy) {
            try {
                // Simple client-side auto-planning implementation
                const result = {
                    success: true,
                    plan: {
                        strategy: strategy,
                        stepTimings: []
                    }
                };
                
                // Calculate step dependencies and earliest start times
                const stepDependencies = calculateStepDependencies(programData);
                const earliestTimes = calculateEarliestStartTimes(stepDependencies, programData);
                
                // Apply strategy-specific logic
                if (strategy === 'synchronized_finish') {
                    result.plan.stepTimings = applySynchronizedFinish(earliestTimes, programData);
                } else if (strategy === 'cover') {
                    result.plan.stepTimings = applyCoverStrategy(earliestTimes, programData);
                } else if (strategy === 'minimize_length') {
                    result.plan.stepTimings = applyMinimizeLength(earliestTimes, programData);
                }
                
                return result;
                
            } catch (error) {
                return {
                    success: false,
                    error: error.message
                };
            }
        }
        
        function calculateStepDependencies(programData) {
            const dependencies = {};
            
            // Simple dependency calculation based on track order
            programData.tracks.forEach(track => {
                track.steps.forEach((step, index) => {
                    dependencies[step.stepId] = [];
                    
                    // Depend on previous step in same track
                    if (index > 0) {
                        dependencies[step.stepId].push(track.steps[index - 1].stepId);
                    }
                });
            });
            
            return dependencies;
        }
        
        function calculateEarliestStartTimes(dependencies, programData) {
            const times = {};
            const stepDurations = {};
            
            // Build step durations map
            programData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    stepDurations[step.stepId] = step.duration.seconds || 0;
                });
            });
            
            function getEarliestTime(stepId, visited = new Set()) {
                if (visited.has(stepId)) return 0; // Cycle prevention
                if (times[stepId] !== undefined) return times[stepId];
                
                visited.add(stepId);
                
                let maxPredecessorEnd = 0;
                
                (dependencies[stepId] || []).forEach(depStepId => {
                    const depStart = getEarliestTime(depStepId, new Set(visited));
                    const depDuration = stepDurations[depStepId] || 0;
                    maxPredecessorEnd = Math.max(maxPredecessorEnd, depStart + depDuration);
                });
                
                times[stepId] = maxPredecessorEnd;
                return maxPredecessorEnd;
            }
            
            // Calculate for all steps
            programData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    getEarliestTime(step.stepId);
                });
            });
            
            return times;
        }
        
        function applySynchronizedFinish(earliestTimes, programData) {
            const stepTimings = [];
            
            // Calculate track end times at earliest schedule
            const trackEndTimes = {};
            programData.tracks.forEach(track => {
                let trackEnd = 0;
                track.steps.forEach(step => {
                    const startTime = earliestTimes[step.stepId] || 0;
                    const duration = step.duration.seconds || 0;
                    trackEnd = Math.max(trackEnd, startTime + duration);
                });
                trackEndTimes[track.trackId] = trackEnd;
            });
            
            // Find maximum end time
            const maxEndTime = Math.max(...Object.values(trackEndTimes));
            
            // Delay shorter tracks to synchronize finish
            programData.tracks.forEach(track => {
                const trackDelay = maxEndTime - trackEndTimes[track.trackId];
                
                track.steps.forEach(step => {
                    const originalStart = earliestTimes[step.stepId] || 0;
                    const newStart = originalStart + trackDelay;
                    const duration = step.duration.seconds || 0;
                    
                    stepTimings.push({
                        stepId: step.stepId,
                        trackId: track.trackId,
                        startTime: newStart,
                        duration: duration,
                        endTime: newStart + duration
                    });
                });
            });
            
            return stepTimings;
        }
        
        function applyCoverStrategy(earliestTimes, programData) {
            const stepTimings = [];
            let currentTime = 0;
            let lastScheduledTrack = null;
            let lastScheduledStep = null;
            
            // Prepare all steps with metadata
            const allSteps = [];
            programData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    allSteps.push({
                        ...step,
                        trackId: track.trackId,
                        trackName: track.name,
                        trackPriority: track.priority || 100,
                        earliestTime: earliestTimes[step.stepId] || 0,
                        isCommercial: track.trackId === 'commercial-breaks',
                        scheduled: false
                    });
                });
            });
            
            // Keep scheduling until all steps are placed
            while (stepTimings.length < allSteps.length) {
                const availableSteps = allSteps.filter(step => 
                    !step.scheduled && step.earliestTime <= currentTime
                );
                
                if (availableSteps.length === 0) {
                    // No steps available yet, advance time to next earliest step
                    const nextSteps = allSteps.filter(step => !step.scheduled);
                    if (nextSteps.length === 0) break;
                    
                    const nextEarliestTime = Math.min(...nextSteps.map(s => s.earliestTime));
                    currentTime = nextEarliestTime;
                    continue;
                }
                
                // Choose the best next step using priority rules
                let bestStep = null;
                
                // Rule 1: Avoid consecutive commercials at all costs
                const nonCommercialSteps = availableSteps.filter(s => !s.isCommercial);
                if (lastScheduledStep?.isCommercial && nonCommercialSteps.length > 0) {
                    bestStep = chooseBestStep(nonCommercialSteps, lastScheduledTrack);
                } else {
                    // Rule 2: Prefer different tracks to create variety
                    const differentTrackSteps = availableSteps.filter(s => s.trackId !== lastScheduledTrack);
                    if (differentTrackSteps.length > 0) {
                        // Among different tracks, prefer non-commercials
                        const nonCommercialDifferentTrack = differentTrackSteps.filter(s => !s.isCommercial);
                        if (nonCommercialDifferentTrack.length > 0) {
                            bestStep = chooseBestStep(nonCommercialDifferentTrack, lastScheduledTrack);
                        } else {
                            bestStep = chooseBestStep(differentTrackSteps, lastScheduledTrack);
                        }
                    } else {
                        // Rule 3: If no different track available, use same track but avoid consecutive commercials
                        bestStep = chooseBestStep(availableSteps, lastScheduledTrack);
                    }
                }
                
                if (!bestStep) {
                    // Fallback: just pick the first available step
                    bestStep = availableSteps[0];
                }
                
                // Schedule the chosen step
                const duration = bestStep.duration.seconds || 0;
                const endTime = currentTime + duration;
                
                stepTimings.push({
                    stepId: bestStep.stepId,
                    trackId: bestStep.trackId,
                    startTime: currentTime,
                    duration: duration,
                    endTime: endTime
                });
                
                // Update tracking variables
                bestStep.scheduled = true;
                currentTime = endTime;
                lastScheduledTrack = bestStep.trackId;
                lastScheduledStep = bestStep;
            }
            
            return stepTimings;
        }
        
        function chooseBestStep(candidateSteps, lastTrackId) {
            if (candidateSteps.length === 0) return null;
            if (candidateSteps.length === 1) return candidateSteps[0];
            
            // Sort by priority (lower number = higher priority), then by earliest time
            candidateSteps.sort((a, b) => {
                if (a.trackPriority !== b.trackPriority) {
                    return a.trackPriority - b.trackPriority;
                }
                return a.earliestTime - b.earliestTime;
            });
            
            return candidateSteps[0];
        }
        
        function applyMinimizeLength(earliestTimes, programData) {
            const stepTimings = [];
            
            // Use earliest times and minimum durations
            programData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    const startTime = earliestTimes[step.stepId] || 0;
                    const duration = step.duration.seconds || 0;
                    
                    stepTimings.push({
                        stepId: step.stepId,
                        trackId: track.trackId,
                        startTime: startTime,
                        duration: duration,
                        endTime: startTime + duration
                    });
                });
            });
            
            return stepTimings;
        }
        
        function updateTimelineWithPlan(plan) {
            // Update timeline data with new step timings
            plan.stepTimings.forEach(timing => {
                // Find the step in timeline data and update its times
                timelineData.tracks.forEach(track => {
                    if (track.trackId === timing.trackId) {
                        track.steps.forEach(step => {
                            if (step.stepId === timing.stepId) {
                                step.startTime = timing.startTime;
                                step.duration = timing.duration;
                                step.endTime = timing.endTime;
                                
                                // Mark as auto-planned
                                step.autoPlanned = true;
                            }
                        });
                    }
                });
            });
            
            // Recalculate total duration
            let maxEndTime = 0;
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    maxEndTime = Math.max(maxEndTime, step.endTime);
                });
            });
            timelineData.totalDuration = maxEndTime;
            
            // Update the duration display
            updateDurationDisplay();
        }
        
        function updateDurationDisplay() {
            // Format the new duration
            function formatDuration(seconds) {
                if (seconds >= 3600) {
                    const hours = seconds / 3600;
                    return hours % 1 === 0 ? `${Math.floor(hours)} hours` : `${hours.toFixed(1)} hours`;
                } else if (seconds >= 60) {
                    const minutes = seconds / 60;
                    return minutes % 1 === 0 ? `${Math.floor(minutes)} minutes` : `${minutes.toFixed(1)} minutes`;
                } else {
                    return `${Math.floor(seconds)} seconds`;
                }
            }
            
            const newFormattedDuration = formatDuration(timelineData.totalDuration || 0);
            
            // Find the duration display element - it's the green text in the first column
            const durationElement = document.querySelector('.text-dag-green');
            if (durationElement) {
                // Check if this is the program duration scenario (has blue text above)
                const parentDiv = durationElement.closest('.flex.flex-col.items-center');
                const hasFixedDuration = parentDiv && parentDiv.querySelector('.text-blue-600');
                
                if (hasFixedDuration) {
                    // Update estimated duration text (Est: X)
                    durationElement.textContent = `Est: ${newFormattedDuration}`;
                } else {
                    // Update regular estimated duration
                    durationElement.textContent = newFormattedDuration;
                }
            }
        }
        
        function resetPlan() {
            // Check if we have original data stored (either from auto-plan or optimization)
            if (originalTimelineData) {
                // Restore original timeline data
                timelineData.tracks = JSON.parse(JSON.stringify(originalTimelineData.tracks));
                timelineData.totalDuration = originalTimelineData.totalDuration;
                
                // Clear the stored original data
                originalTimelineData = null;
                
                // Refresh all views
                refreshAllViews();
                
                // Update duration display
                updateDurationDisplay();
                
                showToast('Timeline reset to original plan', 'info');
            } else {
                showToast('No changes to reset', 'warning');
            }
        }
        
        // Event listeners for auto-plan controls
        document.getElementById('auto-plan-btn').addEventListener('click', autoPlan);
        document.getElementById('reset-plan-btn').addEventListener('click', resetPlan);
        
        // Close modal when clicking outside
        document.getElementById('settings-modal').addEventListener('click', function(e) {
            if (e.target === this) {
                closeSettingsModal();
            }
        });
        
        // Load settings on page load
        document.addEventListener('DOMContentLoaded', function() {
            loadUserSettings();
        });
        
        // Handle window resize to ensure DAG SVG stays responsive
        window.addEventListener('resize', function() {
            // Debounce resize events to avoid excessive re-rendering
            clearTimeout(window.resizeTimeout);
            window.resizeTimeout = setTimeout(function() {
                if (activeViews.has('dag')) {
                    renderDAG();
                }
                if (activeViews.has('timeline')) {
                    updateTimeline(currentScale);
                }
            }, 100);
        });
        
        // Execution state variables
        let executionState = 'stopped'; // 'stopped', 'running', 'paused'
        let currentTime = 0;
        let executionTimer = null;
        let executionSpeed = 1;
        let currentTimelinePosition = null;
        
        // Time format variables
        let timeFormat = 'relative-minutes'; // Default to relative minutes
        let programStartTime = new Date(); // Default to current time
        let programStartTimeString = '09:00'; // Default start time string
        let executionStartTime = null; // Actual time when execution starts (for time-of-day modes)
        
        // Set up SVG
        const svg = d3.select("#dag-svg");
        const width = parseInt(svg.style("width"));
        const height = parseInt(svg.style("height"));
        
        // Set up timeline SVG
        const timelineSvg = d3.select("#timeline-svg");
        const timelineWidth = parseInt(timelineSvg.style("width"));
        const timelineHeight = parseInt(timelineSvg.style("height"));
        
        // Initialize time format selector and start time picker
        function initializeTimeFormatControls() {
            const timeFormatSelector = document.getElementById('time-format-selector');
            
            timeFormatSelector.addEventListener('change', function() {
                timeFormat = this.value;
                updateTimeline(currentScale);
            });
        }
        
        function updateTimeFormatInfo() {
            // Function removed - timeUnitInfo element no longer exists
        }
        
        function formatTimeForDisplay(seconds, format = null) {
            const currentFormat = format || timeFormat;
            
            // For Time of Day formats, determine if this is a past or future time
            let effectiveExecutionStartTime = executionStartTime;
            if (currentFormat.startsWith('time-of-day') && executionStartTime && executionState !== 'stopped') {
                // If this time point is in the future (hasn't happened yet), 
                // calculate what the actual start time should be for realistic future times
                if (seconds > currentTime) {
                    const now = new Date();
                    const timeUntilThisEvent = seconds - currentTime;
                    effectiveExecutionStartTime = new Date(now.getTime() - (currentTime * 1000));
                }
                // For past times (seconds <= currentTime), use original executionStartTime
            }
            
            switch(currentFormat) {
                case 'relative-seconds':
                    // Format as seconds (e.g., "150s", "3600s")
                    return Math.floor(seconds) + 's';
                    
                case 'relative-minutes':
                    // Format as minutes and seconds (e.g., "2:30", "45:15")
                    const minutes = Math.floor(seconds / 60);
                    const secs = Math.floor(seconds % 60);
                    return `${minutes}:${secs.toString().padStart(2, '0')}`;
                    
                case 'relative-hours':
                    // Format as hours, minutes, and seconds (e.g., "1:23:45", "0:45:30")
                    const hours = Math.floor(seconds / 3600);
                    const mins = Math.floor((seconds % 3600) / 60);
                    const remainingSecs = Math.floor(seconds % 60);
                    return `${hours}:${mins.toString().padStart(2, '0')}:${remainingSecs.toString().padStart(2, '0')}`;
                    
                case 'time-of-day-24h':
                    // Format as 24-hour time of day
                    if (effectiveExecutionStartTime) {
                        const absoluteTime = new Date(effectiveExecutionStartTime.getTime() + seconds * 1000);
                        return absoluteTime.toLocaleTimeString('en-US', { 
                            hour12: false, 
                            hour: '2-digit', 
                            minute: '2-digit',
                            second: totalDuration <= 3600 ? '2-digit' : undefined
                        });
                    } else {
                        // Fallback to relative format if execution hasn't started
                        return formatTimeForDisplay(seconds, 'relative-minutes');
                    }
                    
                case 'time-of-day-12h':
                    // Format as 12-hour time of day
                    if (effectiveExecutionStartTime) {
                        const absoluteTime = new Date(effectiveExecutionStartTime.getTime() + seconds * 1000);
                        return absoluteTime.toLocaleTimeString('en-US', { 
                            hour12: true, 
                            hour: 'numeric', 
                            minute: '2-digit',
                            second: totalDuration <= 3600 ? '2-digit' : undefined
                        });
                    } else {
                        // Fallback to relative format if execution hasn't started
                        return formatTimeForDisplay(seconds, 'relative-minutes');
                    }
                    
                default:
                    // Default to relative minutes
                    return formatTimeForDisplay(seconds, 'relative-minutes');
            }
        }
        
        // Execution Control Functions
        function startExecution() {
            if (executionState === 'stopped') {
                currentTime = 0;
                // Capture the actual execution start time for Time of Day formats
                executionStartTime = new Date();
            }
            
            // Update timeline display if using Time of Day format
            // This will recalculate future times while preserving past times
            if (timeFormat.startsWith('time-of-day')) {
                updateTimeline(currentScale);
            }
            
            executionState = 'running';
            updateExecutionControls();
            updateExecutionStatus('Running');
            
            const speed = parseFloat(document.getElementById('speed-control').value);
            executionSpeed = speed;
            
            // Start the execution timer (update every 100ms, but advance time based on speed)
            executionTimer = setInterval(updateExecution, 100);
        }
        
        function pauseExecution() {
            executionState = 'paused';
            updateExecutionControls();
            updateExecutionStatus('Paused');
            
            if (executionTimer) {
                clearInterval(executionTimer);
                executionTimer = null;
            }
        }
        
        function stopExecution() {
            executionState = 'stopped';
            currentTime = 0;
            updateExecutionControls();
            updateExecutionStatus('Stopped');
            updateTimeDisplay(0);
            
            if (executionTimer) {
                clearInterval(executionTimer);
                executionTimer = null;
            }
            
            // Reset all visual states
            resetExecutionVisuals();
        }
        
        function updateExecution() {
            // Advance time based on speed (0.1 seconds per tick * speed multiplier)
            currentTime += 0.1 * executionSpeed;
            
            // Check if execution is complete
            if (currentTime >= timelineData.totalDuration) {
                currentTime = timelineData.totalDuration;
                stopExecution();
                updateExecutionStatus('Completed');
            }
            
            updateTimeDisplay(currentTime);
            updateExecutionVisuals();
        }
        
        function updateExecutionControls() {
            const startBtn = document.getElementById('start-btn');
            const pauseBtn = document.getElementById('pause-btn');
            const stopBtn = document.getElementById('stop-btn');
            
            if (executionState === 'stopped') {
                startBtn.disabled = false;
                pauseBtn.disabled = true;
                stopBtn.disabled = true;
            } else if (executionState === 'running') {
                startBtn.disabled = true;
                pauseBtn.disabled = false;
                stopBtn.disabled = false;
            } else if (executionState === 'paused') {
                startBtn.disabled = false;
                pauseBtn.disabled = true;
                stopBtn.disabled = false;
            }
        }
        
        function updateExecutionStatus(status) {
            document.getElementById('execution-status').textContent = status;
        }
        
        function updateTimeDisplay(time) {
            const minutes = Math.floor(time / 60);
            const seconds = Math.floor(time % 60);
            const display = minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
            document.getElementById('current-time-display').textContent = display;
        }
        
        function updateExecutionVisuals() {
            updateDAGExecutionState();
            updateTimelineExecutionState();
            updateItineraryExecutionState();
        }
        
        function resetExecutionVisuals() {
            // Reset DAG nodes
            d3.selectAll('.node').classed('active completed', false);
            
            // Reset timeline task bars
            d3.selectAll('.task-bar').classed('active completed', false);
            
            // Remove current time line
            d3.selectAll('.current-time-line').remove();
            
            // Reset itinerary items
            document.querySelectorAll('.timeline-item').forEach(item => {
                item.classList.remove('active', 'completed');
            });
        }
        
        function updateDAGExecutionState() {
            // Get currently active and completed steps
            const activeSteps = getCurrentlyActiveSteps();
            const completedSteps = getCompletedSteps();
            
            // Update node visual states
            d3.selectAll('.node').each(function(d) {
                const node = d3.select(this);
                node.classed('active', activeSteps.includes(d.id));
                node.classed('completed', completedSteps.includes(d.id));
            });
        }
        
        function updateTimelineExecutionState() {
            // Remove existing current time line
            d3.selectAll('.current-time-line').remove();
            
            // Add current time line if execution is active
            if (executionState === 'running' || executionState === 'paused') {
                const timeScale = d3.scaleLinear()
                    .domain([0, timelineData.totalDuration])
                    .range([0, calculateTimelineWidth(currentScale)]);
                
                const timelineG = timelineSvg.select('g');
                timelineG.append('line')
                    .attr('class', 'current-time-line')
                    .attr('x1', timeScale(currentTime))
                    .attr('y1', 0)
                    .attr('x2', timeScale(currentTime))
                    .attr('y2', timelineHeight - 80);
            }
            
            // Update task bar states
            const activeSteps = getCurrentlyActiveSteps();
            const completedSteps = getCompletedSteps();
            
            d3.selectAll('.task-bar').each(function(d) {
                const bar = d3.select(this);
                bar.classed('active', activeSteps.includes(d.stepId));
                bar.classed('completed', completedSteps.includes(d.stepId));
            });
        }
        
        function updateItineraryExecutionState() {
            const activeSteps = getCurrentlyActiveSteps();
            const completedSteps = getCompletedSteps();
            
            // Update timeline items and scroll to active item
            let activeItem = null;
            document.querySelectorAll('.timeline-item').forEach((item, index) => {
                const stepId = item.dataset.stepId;
                const isActive = activeSteps.includes(stepId);
                const isCompleted = completedSteps.includes(stepId);
                
                item.classList.toggle('active', isActive);
                item.classList.toggle('completed', isCompleted);
                
                if (isActive && !activeItem) {
                    activeItem = item;
                }
            });
            
            // Auto-scroll to active item
            if (activeItem) {
                activeItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
            }
        }
        
        function getCurrentlyActiveSteps() {
            const activeSteps = [];
            
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    if (currentTime >= step.startTime && currentTime <= step.endTime) {
                        activeSteps.push(step.stepId);
                    }
                });
            });
            
            return activeSteps;
        }
        
        function getCompletedSteps() {
            const completedSteps = [];
            
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    if (currentTime > step.endTime) {
                        completedSteps.push(step.stepId);
                    }
                });
            });
            
            return completedSteps;
        }
        
        // Event listeners for execution controls
        document.getElementById('start-btn').addEventListener('click', startExecution);
        document.getElementById('pause-btn').addEventListener('click', pauseExecution);
        document.getElementById('stop-btn').addEventListener('click', stopExecution);
        
        // Speed control
        document.getElementById('speed-control').addEventListener('change', function() {
            executionSpeed = parseFloat(this.value);
        });
        
        // Initialize execution controls
        updateExecutionControls();
        
        // View Toggle Functionality - Multi-selection mode
        let activeViews = new Set(); // Will be set by user settings
        
        function updateView() {
            const dagPanel = document.getElementById('dag-panel');
            const timelinePanel = document.getElementById('timeline-panel');
            const resourcesPanel = document.getElementById('resources-panel');
            const itineraryPanel = document.getElementById('itinerary-panel');
            const panelsContainer = document.getElementById('panels-container');
            
            // If no views are selected, show all views
            if (activeViews.size === 0) {
                activeViews = new Set(['dag', 'timeline', 'resources', 'itinerary']);
            }
            
            // Update button states based on active views
            document.querySelectorAll('.view-toggle').forEach(btn => {
                const viewType = btn.dataset.view;
                // Individual view buttons are active when their view is in the set
                if (activeViews.has(viewType)) {
                    btn.classList.remove('text-gray-700');
                    btn.classList.add('text-white');
                    btn.style.backgroundColor = '#4B46B9';
                } else {
                    btn.classList.remove('text-white');
                    btn.classList.add('text-gray-700');
                    btn.style.backgroundColor = '';
                }
            });
            
            // Show/hide panels based on active views
            dagPanel.style.display = activeViews.has('dag') ? 'block' : 'none';
            timelinePanel.style.display = activeViews.has('timeline') ? 'block' : 'none';
            resourcesPanel.style.display = activeViews.has('resources') ? 'block' : 'none';
            itineraryPanel.style.display = activeViews.has('itinerary') ? 'block' : 'none';
            
            // Adjust layout based on number of active views
            const visibleCount = activeViews.size;
            
            // Reset classes
            panelsContainer.classList.remove('justify-center', 'lg:flex-row');
            dagPanel.classList.remove('flex-1');
            timelinePanel.classList.remove('flex-1');
            resourcesPanel.classList.remove('flex-1');
            itineraryPanel.classList.remove('flex-1');
            
            if (visibleCount > 1) {
                // Multiple views: use flex layout
                panelsContainer.classList.add('lg:flex-row');
                if (activeViews.has('dag')) dagPanel.classList.add('flex-1');
                if (activeViews.has('timeline')) timelinePanel.classList.add('flex-1');
                if (activeViews.has('resources')) resourcesPanel.classList.add('flex-1');
                if (activeViews.has('itinerary')) itineraryPanel.classList.add('flex-1');
            }
            // Single view: panels will use full width automatically
            
            // Re-render active visualizations
            setTimeout(() => {
                if (activeViews.has('dag')) {
                    renderDAG();
                }
                if (activeViews.has('timeline')) {
                    updateTimeline(currentScale);
                }
                if (activeViews.has('resources')) {
                    generateResourceUsage();
                }
                if (activeViews.has('itinerary')) {
                    generateItinerary();
                }
            }, 100);
        }
        
        // Add event listeners for view toggle buttons
        document.querySelectorAll('.view-toggle').forEach(button => {
            button.addEventListener('click', function() {
                const viewType = this.dataset.view;
                
                // Toggle individual view
                if (activeViews.has(viewType)) {
                    activeViews.delete(viewType);
                } else {
                    activeViews.add(viewType);
                }
                
                updateView();
            });
        });
        
        // Initialize views
        updateView();
        
        // Core visualization functions
        function renderDAG() {
            // Get updated SVG container dimensions
            const dagSvgElement = document.getElementById('dag-svg');
            const newWidth = dagSvgElement.clientWidth;
            const newHeight = dagSvgElement.clientHeight;
            
            // Don't override the 100% width/height - let the SVG stay responsive
            // The SVG will automatically size to its container
            
            // Update simulation center force with new dimensions
            simulation.force("center", d3.forceCenter(newWidth / 2, newHeight / 2));
            
            // Update initial positions for key nodes based on new dimensions
            nodes.forEach(node => {
                const dims = calculateNodeDimensions(node);
                const nodeMargin = Math.max(dims.width, dims.height) / 2 + 20;
                
                if (node.type === 'start') {
                    // Keep Program Start in upper left, but adjust if space is smaller
                    node.x = Math.min(node.x || nodeMargin, nodeMargin);
                    node.y = Math.min(node.y || nodeMargin, nodeMargin);
                } else if (node.id === lastFinishingStep) {
                    // Update last finishing step position for new width/height
                    const targetX = newWidth - nodeMargin;
                    const targetY = newHeight - nodeMargin;
                    // Only update if we have more space or need to fit in smaller space
                    if (newWidth > width || node.x > newWidth - nodeMargin) {
                        node.x = Math.max(Math.min(node.x || targetX, targetX), nodeMargin);
                    }
                    if (newHeight > height || node.y > newHeight - nodeMargin) {
                        node.y = Math.max(Math.min(node.y || targetY, targetY), nodeMargin);
                    }
                }
                // Ensure all nodes stay within new bounds with proper margins
                node.x = Math.max(nodeMargin, Math.min(node.x || Math.random() * newWidth, newWidth - nodeMargin));
                node.y = Math.max(nodeMargin, Math.min(node.y || Math.random() * newHeight, newHeight - nodeMargin));
            });
            
            // Update zoom translateExtent for new dimensions - allow generous panning
            // Create a much larger panning area to accommodate all possible node positions
            const panningMargin = Math.max(newWidth, newHeight) * 2;
            zoom.translateExtent([
                [-panningMargin, -panningMargin], 
                [newWidth + panningMargin, newHeight + panningMargin]
            ]);
            
            // Gently restart simulation to adjust to new space
            simulation.alpha(0.2).restart();
        }
        
        // Create arrow marker
        svg.append("defs").append("marker")
            .attr("id", "arrowhead")
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 70)
            .attr("refY", 0)
            .attr("markerWidth", 6)
            .attr("markerHeight", 6)
            .attr("orient", "auto")
            .append("path")
            .attr("d", "M0,-5L10,0L0,5")
            .attr("fill", "#666");
        
        // Create zoom group for all graph content
        const zoomGroup = svg.append("g")
            .attr("class", "zoom-group");
        
        // DAG zoom functionality
        let currentDagZoom = 1.0;
        const minDagZoom = 0.1;
        const maxDagZoom = 5.0;
        
        // Create zoom behavior
        const zoom = d3.zoom()
            .scaleExtent([minDagZoom, maxDagZoom])
            .translateExtent([[-2000, -2000], [2000, 2000]]) // Initial generous panning bounds
            .on("start", function() {
                // Add panning visual feedback
                svg.classed("panning", true);
            })
            .on("zoom", function() {
                const transform = d3.event.transform;
                zoomGroup.attr("transform", transform);
                currentDagZoom = transform.k;
                updateDagZoomInfo();
            })
            .on("end", function() {
                // Remove panning visual feedback
                svg.classed("panning", false);
            });
        
        // Apply zoom behavior to SVG (enables both zoom and pan)
        svg.call(zoom);
        
        // Enhanced drag behavior for nodes that prevents conflict with panning
        const drag = d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended);
        
        // Apply drag behavior to nodes
        function applyNodeDrag(nodeSelection) {
            return nodeSelection.call(drag);
        }
        
        // DAG zoom control functions
        function updateDagZoomInfo() {
            document.getElementById('dag-zoom-info').textContent = 'Zoom: ' + currentDagZoom.toFixed(1) + 'x';
            
            // Update button states
            document.getElementById('dag-zoom-out').disabled = currentDagZoom <= minDagZoom + 0.05;
            document.getElementById('dag-zoom-in').disabled = currentDagZoom >= maxDagZoom - 0.05;
        }
        
        function zoomDagIn() {
            svg.transition().duration(300).call(
                zoom.scaleBy, 1.5
            );
        }
        
        function zoomDagOut() {
            svg.transition().duration(300).call(
                zoom.scaleBy, 1 / 1.5
            );
        }
        
        function resetDagView() {
            svg.transition().duration(500).call(
                zoom.transform,
                d3.zoomIdentity.translate(0, 0).scale(1)
            );
        }
        
        // Add event listeners for DAG zoom controls
        document.getElementById('dag-zoom-in').addEventListener('click', zoomDagIn);
        document.getElementById('dag-zoom-out').addEventListener('click', zoomDagOut);
        document.getElementById('dag-zoom-reset').addEventListener('click', resetDagView);
        
        // Initialize zoom info
        updateDagZoomInfo();
        
        // Create tooltip
        const tooltip = d3.select("body").append("div")
            .attr("class", "tooltip")
            .style("opacity", 0);
        
        // Tooltip update management for live timing updates
        let tooltipUpdateInterval = null;
        let currentTooltipData = null;
        let currentTooltipType = null; // 'timeline', 'buffer', or 'itinerary'
        let currentMouseEvent = null;
        
        function startTooltipUpdates(data, tooltipType, event) {
            currentTooltipData = data;
            currentTooltipType = tooltipType;
            currentMouseEvent = event;
            
            // Clear any existing interval
            if (tooltipUpdateInterval) {
                clearInterval(tooltipUpdateInterval);
            }
            
            // Update tooltip immediately
            updateTooltipContent();
            
            // Set up interval to update tooltip every 100ms during execution
            if (executionState === 'running') {
                tooltipUpdateInterval = setInterval(() => {
                    updateTooltipContent();
                }, 100);
            }
        }
        
        function stopTooltipUpdates() {
            if (tooltipUpdateInterval) {
                clearInterval(tooltipUpdateInterval);
                tooltipUpdateInterval = null;
            }
            currentTooltipData = null;
            currentTooltipType = null;
            currentMouseEvent = null;
        }
        
        function updateTooltipContent() {
            if (!currentTooltipData || !currentTooltipType || !currentMouseEvent) return;
            
            let content;
            if (currentTooltipType === 'timeline') {
                content = getTimelineTooltipContent(currentTooltipData);
            } else if (currentTooltipType === 'buffer') {
                content = getBufferTooltipContent(currentTooltipData.step, currentTooltipData.bufferType);
            } else if (currentTooltipType === 'itinerary') {
                content = getItineraryTooltipContent(currentTooltipData);
            } else if (currentTooltipType === 'dag') {
                content = getTooltipContent(currentTooltipData);
            }
            
            if (content) {
                // Show and position the tooltip
                tooltip.transition()
                    .duration(200)
                    .style("opacity", .9);
                tooltip.html(content)
                    .style("left", (currentMouseEvent.pageX + 10) + "px")
                    .style("top", (currentMouseEvent.pageY - 28) + "px");
            }
        }
        
        function showTooltip(content, event) {
            tooltip.transition()
                .duration(200)
                .style("opacity", .9);
            tooltip.html(content)
                .style("left", (event.pageX + 10) + "px")
                .style("top", (event.pageY - 28) + "px");
        }
        
        function hideTooltip() {
            stopTooltipUpdates();
            tooltip.transition()
                .duration(500)
                .style("opacity", 0);
        }
        
        // Set initial positions for key nodes
        // Find the step that finishes last based on timeline data
        let lastFinishingStep = null;
        let latestEndTime = -1;
        
        if (timelineData && timelineData.tracks) {
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    if (step.endTime > latestEndTime) {
                        latestEndTime = step.endTime;
                        lastFinishingStep = step.stepId;
                    }
                });
            });
        }
        
        // Set initial positions
        nodes.forEach(node => {
            if (node.type === 'start') {
                // Position Program Start in upper left
                node.x = 100;
                node.y = 80;
                node.fx = 100;  // Fix position initially
                node.fy = 80;
            } else if (node.id === lastFinishingStep) {
                // Position last finishing step in lower right
                node.x = width - 120;
                node.y = height - 80;
                node.fx = width - 120;  // Fix position initially
                node.fy = height - 80;
            } else {
                // Give other nodes random starting positions
                node.x = Math.random() * width;
                node.y = Math.random() * height;
            }
        });
        
        // Create simulation with dynamic collision detection
        const simulation = d3.forceSimulation(nodes)
            .force("link", d3.forceLink(edges).id(d => d.id).distance(150))
            .force("charge", d3.forceManyBody().strength(-500))
            .force("center", d3.forceCenter(width / 2, height / 2))
            .force("collision", d3.forceCollide().radius(d => {
                const dims = calculateNodeDimensions(d);
                return Math.max(dims.width, dims.height) / 2 + 10; // Padding around nodes
            }));
        
        // Release fixed positions after simulation settles
        setTimeout(() => {
            nodes.forEach(node => {
                if (node.type === 'start' || node.id === lastFinishingStep) {
                    node.fx = null;
                    node.fy = null;
                }
            });
        }, 2000); // Release after 2 seconds
        
        // Create links
        const link = zoomGroup.append("g")
            .selectAll("line")
            .data(edges)
            .enter().append("line")
            .attr("class", d => "link " + d.type)
            .attr("stroke-width", 2);
        
        // Create nodes with dynamic sizing based on text length
        const nodeGroup = zoomGroup.append("g").selectAll("g")
            .data(nodes)
            .enter().append("g")
            .attr("class", "node-group")
            .call(applyNodeDrag);
        
        // Calculate node dimensions based on text content
        function calculateNodeDimensions(d) {
            const isStart = d.type === 'start';
            const textLength = d.name.length;
            
            // Base dimensions
            let width = isStart ? 100 : 120;
            let height = isStart ? 50 : 60;
            
            // Adjust for longer text - increase width and potentially height
            if (textLength > 15) {
                width = Math.max(width, Math.min(textLength * 6, 200)); // Max width 200px
                // Add height for text that will likely wrap
                if (textLength > 25) {
                    height += 20; // Extra height for wrapped text
                }
                if (textLength > 45) {
                    height += 20; // Even more height for very long text
                }
            }
            
            return { width, height };
        }
        
        // Add rectangles to node groups
        const nodeRects = nodeGroup.append("rect")
            .attr("class", d => "node " + d.type)
            .attr("width", d => calculateNodeDimensions(d).width)
            .attr("height", d => calculateNodeDimensions(d).height)
            .attr("rx", 8)
            .attr("ry", 8)
            .on("mouseover", function(d, i, nodes) {
                const mouseEvent = d3.event;
                startTooltipUpdates(d, 'dag', mouseEvent);
            })
            .on("mouseout", function(d, i, nodes) {
                hideTooltip();
            });
        
        // Add foreignObject for HTML text that can wrap
        const nodeLabels = nodeGroup.append("foreignObject")
            .attr("width", d => calculateNodeDimensions(d).width - 16) // More padding for centering
            .attr("height", d => calculateNodeDimensions(d).height - 16) // More padding for centering
            .attr("x", d => 8) // Centered offset - half of total padding
            .attr("y", d => 8) // Centered offset - half of total padding
            .append("xhtml:div")
            .style("width", d => (calculateNodeDimensions(d).width - 16) + "px")
            .style("height", d => (calculateNodeDimensions(d).height - 16) + "px")
            .style("display", "flex")
            .style("align-items", "center")
            .style("justify-content", "center")
            .style("text-align", "center")
            .style("font-size", "11px")
            .style("font-weight", "bold")
            .style("color", "white")
            .style("line-height", "1.2")
            .style("padding", "4px")
            .style("box-sizing", "border-box")
            .style("word-wrap", "break-word")
            .style("overflow-wrap", "break-word")
            .style("hyphens", "auto")
            .text(d => d.name);
        
        // Store reference to the node elements (both rects and groups)
        const node = nodeRects;
        
        // Create edge labels for timing information
        const edgeLabels = zoomGroup.append("g")
            .selectAll("g")
            .data(edges.filter(d => d.offset !== undefined || d.buffer !== undefined))
            .enter().append("g")
            .attr("class", "edge-label-group");
        
        // Add background rectangles for edge labels
        edgeLabels.append("rect")
            .attr("class", "edge-label-bg")
            .attr("width", d => {
                let text = '';
                if (d.offset !== undefined) text = d.offset + 's';
                if (d.buffer !== undefined) text = d.buffer + 's';
                return Math.max(20, text.length * 6 + 8);
            })
            .attr("height", 16)
            .attr("x", d => {
                let text = '';
                if (d.offset !== undefined) text = d.offset + 's';
                if (d.buffer !== undefined) text = d.buffer + 's';
                return -(Math.max(20, text.length * 6 + 8) / 2);
            })
            .attr("y", -8);
        
        // Add text for edge labels
        edgeLabels.append("text")
            .attr("class", "edge-label")
            .text(d => {
                if (d.offset !== undefined) return d.offset + 's';
                if (d.buffer !== undefined) return d.buffer + 's';
                return '';
            });
        
        // Update positions
        simulation.on("tick", () => {
            link
                .attr("x1", d => d.source.x)
                .attr("y1", d => d.source.y)
                .attr("x2", d => d.target.x)
                .attr("y2", d => d.target.y);
            
            nodeGroup
                .attr("transform", d => {
                    const dims = calculateNodeDimensions(d);
                    const offsetX = dims.width / 2;
                    const offsetY = dims.height / 2;
                    return `translate(${d.x - offsetX},${d.y - offsetY})`;
                });
            
            // Position edge labels at midpoint of edges
            edgeLabels
                .attr("transform", d => {
                    const midX = (d.source.x + d.target.x) / 2;
                    const midY = (d.source.y + d.target.y) / 2;
                    return "translate(" + midX + "," + midY + ")";
                });
        });
        
        // Drag functions with enhanced panning interaction
        function dragstarted(d) {
            if (!d3.event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
            
            // Stop zoom/pan events when dragging a node
            d3.event.sourceEvent.stopPropagation();
            
            // Add visual feedback for dragging - target the parent group
            d3.select(this).classed("dragging", true);
        }
        
        function dragged(d) {
            d.fx = d3.event.x;
            d.fy = d3.event.y;
        }
        
        function dragended(d) {
            if (!d3.event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
            
            // Remove visual feedback for dragging - target the parent group
            d3.select(this).classed("dragging", false);
        }
        
        function getTooltipContent(d) {
            if (d.type === 'start') {
                return '<strong>' + d.name + '</strong><br/>' + d.description;
            } else {
                let content = '<strong>' + d.name + '</strong><br/>';
                content += 'Track: ' + d.track_name + '<br/>';
                if (d.task) content += 'Task: ' + d.task + '<br/>';
                if (d.duration) content += 'Duration: ' + d.duration + '<br/>';
                if (d.description) content += d.description;
                return content;
            }
        }
        
        function getTimelineTooltipContent(d) {
            let content = '<strong>' + d.name + '</strong><br/>';
            const track = (timelineData.tracks || []).find(t => t.steps.includes(d));
            if (track) content += 'Track: ' + track.name + '<br/>';
            
            // Format time
            const formatTime = (seconds) => {
                const minutes = Math.floor(seconds / 60);
                const secs = Math.floor(seconds % 60);
                if (minutes > 0) {
                    return minutes + 'm ' + secs + 's';
                } else {
                    return secs + 's';
                }
            };
            
            content += 'Start: ' + formatTime(d.startTime) + '<br/>';
            content += 'Duration: ' + formatTime(d.duration) + '<br/>';
            content += 'End: ' + formatTime(d.endTime) + '<br/>';
            
            // Add elapsed and remaining time information
            if (executionState === 'running' || executionState === 'paused') {
                content += '<br/><strong>Execution Status:</strong><br/>';
                
                if (currentTime < d.startTime) {
                    // Step hasn't started yet
                    const timeUntilStart = d.startTime - currentTime;
                    content += 'Status: <span style="color: #6c757d;">Pending</span><br/>';
                    content += 'Starts in: ' + formatTime(timeUntilStart) + '<br/>';
                } else if (currentTime >= d.startTime && currentTime <= d.endTime) {
                    // Step is currently running
                    const elapsed = currentTime - d.startTime;
                    const remaining = d.endTime - currentTime;
                    content += 'Status: <span style="color: #f39c12;">Active</span><br/>';
                    content += 'Elapsed: ' + formatTime(elapsed) + '<br/>';
                    content += 'Remaining: ' + formatTime(remaining) + '<br/>';
                    
                    // Add progress percentage
                    const progress = (elapsed / d.duration) * 100;
                    content += 'Progress: ' + progress.toFixed(1) + '%<br/>';
                } else {
                    // Step has completed
                    const completedFor = currentTime - d.endTime;
                    content += 'Status: <span style="color: #28a745;">Completed</span><br/>';
                    content += 'Finished: ' + formatTime(completedFor) + ' ago<br/>';
                }
            } else {
                content += '<br/><strong>Timeline Position:</strong><br/>';
                content += 'Position: ' + formatTime(d.startTime) + ' - ' + formatTime(d.endTime) + '<br/>';
            }
            
            if (d.task) content += '<br/>Task: ' + d.task + '<br/>';
            
            // Add flex information
            if (d.flex && d.flex.enabled) {
                content += '<br/><strong>Flexible Step:</strong><br/>';
                content += '<span style="color: #10b981;">⚡ Flex Enabled</span><br/>';
                content += 'Mode: ' + (d.flex.mode || 'fill') + '<br/>';
                if (d.flex.minDuration) {
                    content += 'Min Duration: ' + formatTime(d.flex.minDuration) + '<br/>';
                }
                if (d.flex.maxDuration) {
                    content += 'Max Duration: ' + formatTime(d.flex.maxDuration) + '<br/>';
                }
                content += 'Description: This step can expand to fill gaps or adjust timing<br/>';
            }
            
            // Add optimization information
            if (d.optimized && d.staggerDelay) {
                content += '<br/><strong>Optimization:</strong><br/>';
                content += '<span style="color: #f59e0b;">🔧 Optimized</span><br/>';
                content += 'Delay applied: ' + formatTime(d.staggerDelay) + '<br/>';
                content += 'Original start: ' + formatTime(d.originalStartTime || d.startTime - d.staggerDelay) + '<br/>';
            }
            
            if (d.description) content += '<br/>' + d.description;
            
            return content;
        }
        
        function getBufferTooltipContent(d, bufferType) {
            const bufferData = bufferType === 'pre' ? d.preBuffer : d.postBuffer;
            const bufferName = bufferType === 'pre' ? 'Pre-buffer' : 'Post-buffer';
            
            let content = '<strong>' + bufferName + '</strong><br/>';
            content += 'Step: ' + d.name + '<br/>';
            
            // Format time
            const formatTime = (seconds) => {
                const minutes = Math.floor(seconds / 60);
                const secs = Math.floor(seconds % 60);
                if (minutes > 0) {
                    return minutes + 'm ' + secs + 's';
                } else {
                    return secs + 's';
                }
            };
            
            content += 'Start: ' + formatTime(bufferData.startTime) + '<br/>';
            content += 'Duration: ' + formatTime(bufferData.duration) + '<br/>';
            content += 'End: ' + formatTime(bufferData.endTime) + '<br/>';
            
            // Add elapsed and remaining time information for buffers
            if (executionState === 'running' || executionState === 'paused') {
                content += '<br/><strong>Buffer Status:</strong><br/>';
                
                if (currentTime < bufferData.startTime) {
                    // Buffer hasn't started yet
                    const timeUntilStart = bufferData.startTime - currentTime;
                    content += 'Status: <span style="color: #6c757d;">Pending</span><br/>';
                    content += 'Starts in: ' + formatTime(timeUntilStart) + '<br/>';
                } else if (currentTime >= bufferData.startTime && currentTime <= bufferData.endTime) {
                    // Buffer is currently active
                    const elapsed = currentTime - bufferData.startTime;
                    const remaining = bufferData.endTime - currentTime;
                    content += 'Status: <span style="color: #f39c12;">Active</span><br/>';
                    content += 'Elapsed: ' + formatTime(elapsed) + '<br/>';
                    content += 'Remaining: ' + formatTime(remaining) + '<br/>';
                    
                    // Add progress percentage
                    const progress = (elapsed / bufferData.duration) * 100;
                    content += 'Progress: ' + progress.toFixed(1) + '%<br/>';
                } else {
                    // Buffer has completed
                    const completedFor = currentTime - bufferData.endTime;
                    content += 'Status: <span style="color: #28a745;">Completed</span><br/>';
                    content += 'Finished: ' + formatTime(completedFor) + ' ago<br/>';
                }
            } else {
                content += '<br/><strong>Timeline Position:</strong><br/>';
                content += 'Position: ' + formatTime(bufferData.startTime) + ' - ' + formatTime(bufferData.endTime) + '<br/>';
            }
            
            if (bufferData.description) {
                content += '<br/>Description: ' + bufferData.description + '<br/>';
            }
            
            if (bufferData.tasks && bufferData.tasks.length > 0) {
                content += '<br/>Tasks: ' + bufferData.tasks.join(', ');
            }
            
            return content;
        }
        
        // Generate legend
        const tracks = [...new Set(nodes.filter(n => n.type === 'step').map(n => n.track))];
        const colors = [
            '#2E86AB',  // Ocean Blue
            '#A23B72',  // Deep Rose
            '#F18F01',  // Amber
            '#C73E1D',  // Crimson
            '#5D737E',  // Slate Blue
            '#8E44AD',  // Royal Purple
            '#27AE60',  // Emerald
            '#E67E22',  // Orange
            '#34495E',  // Dark Blue Grey
            '#16A085',  // Teal
            '#9B59B6',  // Violet
            '#F39C12'   // Golden Yellow
        ];
        
        const legendItems = d3.select("#legend-items");
        tracks.forEach((track, i) => {
            const trackNodes = nodes.filter(n => n.track === track);
            const trackName = trackNodes.length > 0 ? trackNodes[0].track_name : track;
            
            legendItems.append("span")
                .attr("class", "inline-block m-1 py-1 px-3 rounded text-xs font-bold text-white")
                .style("background-color", colors[i % colors.length])
                .text(trackName);
        });
        
        // Color nodes by track
        node.style("fill", d => {
            if (d.type === 'start') return '#2C3E50';  // Dark blue-grey for program start
            const trackIndex = tracks.indexOf(d.track);
            return colors[trackIndex % colors.length];
        });
        
        // Timeline Visualization
        const margin = { top: 50, right: 50, bottom: 30, left: """ + str(dynamic_left_margin) + """ };
        const minTimelineWidth = 800; // Minimum timeline width
        
        // Timeline scale management
        let currentScale = 1.0;
        let originalTimelineData = null; // Store original timeline state for undo functionality
        let basePixelsPerSecond = Math.max(2, 1200 / Math.max(timelineData.totalDuration || 0, 600));
        
        function calculateTimelineWidth(scale) {
            const pixelsPerSecond = basePixelsPerSecond * scale;
            const calculatedWidth = (timelineData.totalDuration || 0) * pixelsPerSecond;
            
            // Prevent extremely large widths that cause performance issues
            const maxReasonableWidth = 50000; // 50k pixels max for performance
            const effectiveWidth = Math.min(calculatedWidth, maxReasonableWidth);
            
            return Math.max(timelineWidth - margin.left - margin.right, minTimelineWidth, effectiveWidth);
        }
        
        function updateTimeline(scale) {
            currentScale = scale;
            
            // Skip update if scale is too small for performance reasons
            if (scale < 0.005) {
                console.warn('Scale too small, skipping timeline update for performance');
                return;
            }
            
            // Use current timelineData.tracks directly to ensure we get updated data after undo
            const timelineTracks = timelineData.tracks || [];
            const totalDuration = timelineData.totalDuration || 0;
            
            const timelineContentWidth = calculateTimelineWidth(scale);
            const timelineContentHeight = timelineHeight - margin.top - margin.bottom;
            const trackHeight = 40;
            const trackSpacing = 10;
            
            // Determine appropriate time scale based on format and total duration
            let timeUnit = 'seconds';
            let timeScale;
            let timeDivisor = 1;
            let timeAxisFormat;
            
            // Create time axis format function based on selected time format
            if (timeFormat === 'relative-seconds') {
                timeUnit = 'seconds';
                timeAxisFormat = d => formatTimeForDisplay(d, 'relative-seconds');
            } else if (timeFormat === 'relative-minutes') {
                timeUnit = 'minutes';
                timeAxisFormat = d => formatTimeForDisplay(d, 'relative-minutes');
            } else if (timeFormat === 'relative-hours') {
                timeUnit = 'hours';
                timeAxisFormat = d => formatTimeForDisplay(d, 'relative-hours');
            } else if (timeFormat.startsWith('time-of-day')) {
                // Time of day formatting
                timeUnit = timeFormat === 'time-of-day-12h' ? '12h clock' : '24h clock';
                timeAxisFormat = d => formatTimeForDisplay(d, timeFormat);
            }
            
            // Set SVG width to accommodate full timeline
            const fullTimelineWidth = timelineContentWidth + margin.left + margin.right;
            timelineSvg.attr("width", fullTimelineWidth);
            
            // Clear existing timeline
            timelineSvg.selectAll("g").remove();
            
            // Create timeline scales
            timeScale = d3.scaleLinear()
                .domain([0, totalDuration])
                .range([0, timelineContentWidth]);
            
            const trackScale = d3.scaleBand()
                .domain(timelineTracks.map(d => d.trackId))
                .range([0, timelineTracks.length * (trackHeight + trackSpacing)])
                .paddingInner(0.1);
            
            // Create timeline container
            const timelineG = timelineSvg.append("g")
                .attr("transform", `translate(${margin.left},${margin.top})`);
            
            // Create intelligent tick values based on time format and scale
            let tickValues;
            const minTickSpacing = scale < 0.5 ? 150 : 60;
            const maxTicks = Math.floor(timelineContentWidth / minTickSpacing);
            const absoluteMaxTicks = scale < 0.5 ? 50 : 100;
            const effectiveMaxTicks = Math.min(maxTicks, absoluteMaxTicks);
            
            if (timeFormat.startsWith('time-of-day')) {
                // For time of day, create intelligent intervals based on duration
                if (totalDuration <= 3600) {
                    // Less than 1 hour: use 5-15 minute intervals
                    const minuteInterval = Math.max(5, Math.ceil(totalDuration / 60 / effectiveMaxTicks) * 5);
                    tickValues = d3.range(0, totalDuration + 1, minuteInterval * 60);
                } else if (totalDuration <= 14400) {
                    // 1-4 hours: use 15-30 minute intervals
                    const minuteInterval = Math.max(15, Math.ceil(totalDuration / 60 / effectiveMaxTicks / 15) * 15);
                    tickValues = d3.range(0, totalDuration + 1, minuteInterval * 60);
                } else {
                    // More than 4 hours: use 1-2 hour intervals
                    const hourInterval = Math.max(1, Math.ceil(totalDuration / 3600 / effectiveMaxTicks));
                    tickValues = d3.range(0, totalDuration + 1, hourInterval * 3600);
                }
            } else if (timeFormat === 'relative-seconds') {
                // For seconds, use smart intervals
                const secondInterval = Math.max(1, Math.ceil(totalDuration / effectiveMaxTicks));
                tickValues = d3.range(0, totalDuration + 1, secondInterval);
            } else if (timeFormat === 'relative-minutes') {
                // For minutes, use minute-based intervals
                const minuteInterval = Math.max(1, Math.ceil(totalDuration / 60 / effectiveMaxTicks));
                tickValues = d3.range(0, totalDuration + 1, minuteInterval * 60);
            } else if (timeFormat === 'relative-hours') {
                // For hours, use hour-based intervals
                const hourInterval = Math.max(1, Math.ceil(totalDuration / 3600 / effectiveMaxTicks));
                tickValues = d3.range(0, totalDuration + 1, hourInterval * 3600);
            } else {
                // Default fallback - use seconds
                const secondInterval = Math.max(1, Math.ceil(totalDuration / effectiveMaxTicks));
                tickValues = d3.range(0, totalDuration + 1, secondInterval);
            }
            
            // Limit tick values to prevent too many at small scales
            if (tickValues.length > absoluteMaxTicks) {
                const step = Math.ceil(tickValues.length / absoluteMaxTicks);
                tickValues = tickValues.filter((_, i) => i % step === 0);
            }
            
            // Create time axis with dynamic formatting
            const timeAxis = d3.axisTop(timeScale)
                .tickSize(-timelineContentHeight)
                .tickValues(tickValues)
                .tickFormat(timeAxisFormat);
            
            timelineG.append("g")
                .attr("class", "time-axis")
                .call(timeAxis);
            
            // Rotate tick labels for time of day formats if needed
            if (timeFormat.startsWith('time-of-day') && totalDuration > 1800) {
                timelineG.selectAll(".time-axis text")
                    .style("text-anchor", "start")
                    .attr("dx", "0.8em")
                    .attr("dy", "0.15em")
                    .attr("transform", "rotate(45)");
            }
            
            // Create track lanes
            timelineG.selectAll(".track-lane")
                .data(timelineTracks)
                .enter().append("rect")
                .attr("class", "track-lane")
                .attr("x", 0)
                .attr("y", d => trackScale(d.trackId))
                .attr("width", timelineContentWidth)
                .attr("height", trackHeight);
            
            // Create track labels
            timelineG.selectAll(".track-label")
                .data(timelineTracks)
                .enter().append("text")
                .attr("class", "track-label")
                .attr("x", -10)
                .attr("y", d => trackScale(d.trackId) + trackHeight / 2)
                .text(d => d.name);
            
            // Create task bars
            const taskBars = timelineG.selectAll(".task-group")
                .data(timelineTracks)
                .enter().append("g")
                .attr("class", "task-group");
            
            taskBars.selectAll(".task-bar")
                .data(d => d.steps)
                .enter().append("rect")
                .attr("class", "task-bar")
                .attr("x", d => timeScale(d.startTime))
                .attr("y", d => {
                    const track = timelineTracks.find(t => t.steps.includes(d));
                    return trackScale(track.trackId) + 5;
                })
                .attr("width", d => Math.max(2, timeScale(d.duration)))
                .attr("height", trackHeight - 10)
                .style("fill", d => {
                    const track = timelineTracks.find(t => t.steps.includes(d));
                    const trackIndex = timelineTracks.indexOf(track);
                    const baseColor = colors[trackIndex % colors.length];
                    
                    // Slightly different color for flex steps
                    if (d.flex && d.flex.enabled) {
                        const flexColor = d3.color(baseColor);
                        flexColor.opacity = 0.8;
                        return flexColor.toString();
                    }
                    
                    // Keep the same track color even for optimized steps
                    return baseColor;
                })
                .style("stroke", d => {
                    if (d.flex && d.flex.enabled) {
                        return "#10b981"; // Green border for flex steps
                    } else if (d.optimized) {
                        return "#fbbf24"; // Yellow border for optimized steps
                    }
                    return "none";
                })
                .style("stroke-width", d => (d.optimized || (d.flex && d.flex.enabled)) ? "2px" : "0px")
                .style("stroke-dasharray", d => {
                    if (d.flex && d.flex.enabled) {
                        return "2,2"; // Dotted border for flex steps
                    } else if (d.optimized) {
                        return "4,2"; // Dashed border for optimized steps
                    }
                    return "none";
                })
                .style("z-index", "10") // High z-index to appear above overutilization overlay
                .style("pointer-events", "all") // Ensure steps receive mouse events
                .on("mouseover", function(d, i, nodes) {
                    // In D3 v5, use d3.event for the mouse event
                    const mouseEvent = d3.event;
                    startTooltipUpdates(d, 'timeline', mouseEvent);
                })
                .on("mouseout", function(d, i, nodes) {
                    hideTooltip();
                });
            
            // Create task labels (only for longer tasks and higher scales)
            const showLabels = scale >= 1.0;
            if (showLabels) {
                taskBars.selectAll(".task-label")
                    .data(d => d.steps.filter(s => s.duration > 30 / scale)) // Adaptive label threshold
                    .enter().append("text")
                    .attr("class", "task-label")
                    .attr("x", d => timeScale(d.startTime) + timeScale(d.duration) / 2)
                    .attr("y", d => {
                        const track = timelineTracks.find(t => t.steps.includes(d));
                        return trackScale(track.trackId) + trackHeight / 2;
                    })
                    .text(d => {
                        // Calculate available width for text based on step duration
                        const availableWidth = timeScale(d.duration) - 8; // 8px padding
                        const avgCharWidth = 7; // Approximate character width in pixels
                        const maxChars = Math.floor(availableWidth / avgCharWidth);
                        
                        // Only truncate if necessary and if the step is reasonably wide
                        if (maxChars > 10 && d.name.length > maxChars) {
                            return d.name.substring(0, maxChars - 3) + '...';
                        } else if (maxChars <= 10 && d.name.length > 10) {
                            // For very narrow steps, use a minimal truncation
                            return d.name.substring(0, Math.max(3, maxChars - 3)) + '...';
                        }
                        return d.name;
                    });
            }
            
            // Add flex icons for flexible steps
            const showFlexIcons = scale >= 0.5;
            if (showFlexIcons) {
                taskBars.selectAll(".flex-icon")
                    .data(d => d.steps.filter(s => s.flex && s.flex.enabled))
                    .enter().append("text")
                    .attr("class", "flex-icon fas")
                    .attr("x", d => timeScale(d.startTime) + 4)
                    .attr("y", d => {
                        const track = timelineTracks.find(t => t.steps.includes(d));
                        return trackScale(track.trackId) + 16;
                    })
                    .attr("font-family", "Font Awesome 6 Free")
                    .attr("font-weight", "900")
                    .attr("font-size", "10px")
                    .attr("fill", "#10b981")
                    .style("pointer-events", "none")
                    .html("&#xf065;"); // FontAwesome expand arrows icon
            }
            
            // Create buffer bars with desaturated colors (skip at very small scales for performance)
            if (scale >= 0.05) {
                const bufferBars = timelineG.selectAll(".buffer-group")
                    .data(timelineTracks)
                    .enter().append("g")
                    .attr("class", "buffer-group");
                
                // Helper function to desaturate colors
                function desaturateColor(color, factor = 0.3) {
                    const d3Color = d3.color(color);
                    if (d3Color) {
                        const hsl = d3.hsl(d3Color);
                        hsl.s *= factor; // Reduce saturation
                        hsl.l = Math.min(hsl.l + 0.2, 0.9); // Increase lightness
                        return hsl.toString();
                    }
                    return color;
                }
                
                // Render pre-buffers
                bufferBars.selectAll(".pre-buffer")
                    .data(d => d.steps.filter(s => s.preBuffer))
                    .enter().append("rect")
                    .attr("class", "pre-buffer")
                    .attr("x", d => timeScale(d.preBuffer.startTime))
                    .attr("y", d => {
                        const track = timelineTracks.find(t => t.steps.includes(d));
                        return trackScale(track.trackId) + 5;
                    })
                    .attr("width", d => Math.max(1, timeScale(d.preBuffer.duration)))
                    .attr("height", trackHeight - 10)
                    .attr("rx", 4)
                    .attr("ry", 4)
                    .style("fill", d => {
                        const track = timelineTracks.find(t => t.steps.includes(d));
                        const trackIndex = timelineTracks.indexOf(track);
                        const baseColor = colors[trackIndex % colors.length];
                        return desaturateColor(baseColor);
                    })
                    .style("stroke", "#ffffff")
                    .style("stroke-width", "1px")
                    .style("opacity", 0.7)
                    .style("z-index", "9") // High z-index but below main steps
                    .style("pointer-events", "all")
                    .on("mouseover", function(d, i, nodes) {
                        const mouseEvent = d3.event;
                        startTooltipUpdates(d, 'buffer', mouseEvent);
                    })
                    .on("mouseout", function(d, i, nodes) {
                        hideTooltip();
                    });
                
                // Render post-buffers
                bufferBars.selectAll(".post-buffer")
                    .data(d => d.steps.filter(s => s.postBuffer))
                    .enter().append("rect")
                    .attr("class", "post-buffer")
                    .attr("x", d => timeScale(d.postBuffer.startTime))
                    .attr("y", d => {
                        const track = timelineTracks.find(t => t.steps.includes(d));
                        return trackScale(track.trackId) + 5;
                    })
                    .attr("width", d => Math.max(1, timeScale(d.postBuffer.duration)))
                    .attr("height", trackHeight - 10)
                    .attr("rx", 4)
                    .attr("ry", 4)
                    .style("fill", d => {
                        const track = timelineTracks.find(t => t.steps.includes(d));
                        const trackIndex = timelineTracks.indexOf(track);
                        const baseColor = colors[trackIndex % colors.length];
                        return desaturateColor(baseColor);
                    })
                    .style("stroke", "#ffffff")
                    .style("stroke-width", "1px")
                    .style("opacity", 0.7)
                    .style("z-index", "9") // High z-index but below main steps
                    .style("pointer-events", "all")
                    .on("mouseover", function(d, i, nodes) {
                        const mouseEvent = d3.event;
                        startTooltipUpdates(d, 'buffer', mouseEvent);
                    })
                    .on("mouseout", function(d, i, nodes) {
                        hideTooltip();
                    });
            }
            
            // Add overutilization visualization overlay
            addOverutilizationOverlay(timelineG, timeScale, timelineContentHeight);
        }
        
        function addOverutilizationOverlay(timelineG, timeScale, timelineContentHeight) {
            /**
             * Add transparent red overlay showing resource overutilization periods
             */
            console.log('addOverutilizationOverlay called');
            console.log('resourceConstraints:', resourceConstraints);
            
            if (!resourceConstraints || resourceConstraints.length === 0) {
                console.log('No resource constraints found, skipping overlay');
                return; // No resource constraints to check
            }
            
            // Calculate overutilization periods
            const overutilizationPeriods = calculateOverutilizationPeriods();
            console.log('Overutilization periods found:', overutilizationPeriods.length, overutilizationPeriods);
            
            if (overutilizationPeriods.length === 0) {
                console.log('No overutilization detected, skipping overlay');
                return; // No overutilization detected
            }
            
            // Create overutilization overlay group
            const overlayGroup = timelineG.append("g")
                .attr("class", "overutilization-overlay");
            
            console.log('Creating overutilization overlay with', overutilizationPeriods.length, 'periods');
            
            // Add invisible background rectangles for overutilization tooltips (lower layer)
            const backgroundRects = overlayGroup.selectAll(".overutilization-background")
                .data(overutilizationPeriods)
                .enter().append("rect")
                .attr("class", "overutilization-background")
                .attr("x", d => timeScale(d.startTime))
                .attr("y", 0)
                .attr("width", d => timeScale(d.endTime - d.startTime))
                .attr("height", timelineContentHeight)
                .style("fill", "transparent") // Invisible but interactive
                .style("pointer-events", "all")
                .style("z-index", "1")
                .on("mouseover", function(d, i, nodes) {
                    const mouseEvent = d3.event;
                    showOverutilizationTooltip(d, mouseEvent);
                })
                .on("mouseout", function(d, i, nodes) {
                    hideTooltip();
                })
                .on("click", function(d, i, nodes) {
                    // Switch to resources tab when overutilization area is clicked
                    console.log('Overutilization background clicked!', d);
                    console.log('Event:', d3.event);
                    console.log('This element:', this);
                    console.log('All background rects:', document.querySelectorAll('.overutilization-background'));
                    switchToResourcesTab();
                });
            
            // Add visible red rectangles for overutilization visualization (upper layer)
            overlayGroup.selectAll(".overutilization-period")
                .data(overutilizationPeriods)
                .enter().append("rect")
                .attr("class", "overutilization-period")
                .attr("x", d => timeScale(d.startTime))
                .attr("y", 0)
                .attr("width", d => timeScale(d.endTime - d.startTime))
                .attr("height", timelineContentHeight)
                .style("fill", "rgba(220, 38, 38, 0.2)") // Transparent red
                .style("stroke", "rgba(220, 38, 38, 0.6)")
                .style("stroke-width", "1px")
                .style("stroke-dasharray", "3,3")
                .style("pointer-events", "none") // Don't interfere with step tooltips
                .style("z-index", "2"); // Higher than background, lower than steps
        }
        
        function calculateOverutilizationPeriods() {
            /**
             * Calculate time periods where resource constraints are violated
             */
            const periods = [];
            const timeStep = 5; // Check every 5 seconds for granular detection
            
            // Build resource usage timeline
            const resourceUsage = {};
            resourceConstraints.forEach(constraint => {
                resourceUsage[constraint.task] = {
                    maxConcurrent: constraint.maxConcurrent,
                    timeline: []
                };
            });
            
            // Add all step resource usage to timeline
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    if (step.task && resourceUsage[step.task]) {
                        resourceUsage[step.task].timeline.push({
                            startTime: step.startTime,
                            endTime: step.startTime + step.duration,
                            stepName: step.name,
                            stepId: step.stepId
                        });
                    }
                });
            });
            
            // Check each time point for overutilization
            for (let time = 0; time <= timelineData.totalDuration; time += timeStep) {
                const overutilizedResources = [];
                
                Object.keys(resourceUsage).forEach(resourceType => {
                    const resource = resourceUsage[resourceType];
                    let concurrentUsage = 0;
                    const activeSteps = [];
                    
                    resource.timeline.forEach(usage => {
                        // Step is active if time is >= start and < end
                        // Treat steps as ending 0.001 seconds early to avoid edge case overlaps
                        const effectiveEndTime = usage.endTime - 0.001;
                        if (time >= usage.startTime && time < effectiveEndTime) {
                            concurrentUsage++;
                            activeSteps.push(usage.stepName);
                        }
                    });
                    
                    if (concurrentUsage > resource.maxConcurrent) {
                        overutilizedResources.push({
                            resource: resourceType,
                            current: concurrentUsage,
                            max: resource.maxConcurrent,
                            excess: concurrentUsage - resource.maxConcurrent,
                            activeSteps: activeSteps
                        });
                    }
                });
                
                if (overutilizedResources.length > 0) {
                    // Check if this extends an existing period
                    const lastPeriod = periods[periods.length - 1];
                    if (lastPeriod && lastPeriod.endTime >= time - timeStep) {
                        // Extend existing period
                        lastPeriod.endTime = time + timeStep;
                        lastPeriod.overutilizedResources = overutilizedResources; // Update with latest
                    } else {
                        // Start new period
                        periods.push({
                            startTime: time,
                            endTime: time + timeStep,
                            overutilizedResources: overutilizedResources
                        });
                    }
                }
            }
            
            return periods;
        }
        
        function showOverutilizationTooltip(period, event) {
            /**
             * Show tooltip with overutilization details
             */
            const resourceDetails = period.overutilizedResources.map(resource => 
                `<div class="mb-2">
                    <div class="font-semibold text-red-700">${resource.resource}</div>
                    <div class="text-sm text-gray-600">
                        Using ${resource.current}/${resource.max} 
                        (<span class="text-red-600">+${resource.excess} over limit</span>)
                    </div>
                    <div class="text-xs text-gray-500 mt-1">
                        Active: ${resource.activeSteps.join(', ')}
                    </div>
                </div>`
            ).join('');
            
            const startTime = Math.floor(period.startTime / 60) + ':' + String(Math.floor(period.startTime) % 60).padStart(2, '0');
            const endTime = Math.floor(period.endTime / 60) + ':' + String(Math.floor(period.endTime) % 60).padStart(2, '0');
            
            const content = `
                <div class="font-semibold text-red-800 mb-2 flex items-center gap-2">
                    <span>⚠️</span>
                    <span>Resource Overutilization</span>
                </div>
                <div class="text-sm text-gray-600 mb-3">
                    Time: ${startTime} - ${endTime}
                </div>
                ${resourceDetails}
                <div class="text-xs text-gray-500 mt-3 border-t pt-2">
                    <i class="fas fa-mouse-pointer mr-1"></i>Click to view detailed resource usage
                </div>
            `;
            
            showTooltip(content, event);
        }
        
        function switchToResourcesTab() {
            /**
             * Programmatically switch to the resources tab
             */
            console.log('switchToResourcesTab called');
            console.log('Current activeViews:', Array.from(activeViews));
            
            // Ensure resources tab is active
            if (!activeViews.has('resources')) {
                console.log('Adding resources to activeViews');
                activeViews.add('resources');
                updateView();
            } else {
                console.log('Resources tab already active');
            }
            
            console.log('Updated activeViews:', Array.from(activeViews));
            
            // Show a brief toast notification
            showToast('Switched to Resource Usage view', 'info');
        }
        
        function generateItinerary() {
            const itineraryContent = document.getElementById('itinerary-content');
            itineraryContent.innerHTML = '';
            
            // Create array of all events (steps + buffers) with timing
            const events = [];
            
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    // Add main step
                    events.push({
                        time: step.startTime,
                        type: 'step',
                        stepName: step.name,
                        stepId: step.stepId,
                        duration: step.duration,
                        track: track.name,
                        description: step.description || '',
                        task: step.task || ''
                    });
                });
            });
            
            // Sort events by start time
            events.sort((a, b) => a.time - b.time);
            
            // Add summary at the top
            const summaryDiv = document.createElement('div');
            summaryDiv.className = 'bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6';
            summaryDiv.innerHTML = `
                <div class="text-blue-800 font-semibold mb-2 flex items-center gap-2">
                    <span>📋</span>
                    <span>Program Summary</span>
                </div>
                <div class="grid grid-cols-1 sm:grid-cols-3 gap-4 text-sm">
                    <div class="text-blue-700">
                        <span class="font-medium">Total Events:</span>
                        <span class="ml-1">${events.length}</span>
                    </div>
                    <div class="text-blue-700">
                        <span class="font-medium">Total Duration:</span>
                        <span class="ml-1">${Math.floor(timelineData.totalDuration / 60)}m ${timelineData.totalDuration % 60}s</span>
                    </div>
                    <div class="text-blue-700">
                        <span class="font-medium">Tracks:</span>
                        <span class="ml-1">${timelineData.tracks.length}</span>
                    </div>
                </div>
            `;
            itineraryContent.appendChild(summaryDiv);
            
            // Create Flowbite timeline structure
            const timelineOl = document.createElement('ol');
            timelineOl.className = 'relative border-s border-gray-200 dark:border-gray-700';
            
            // Generate timeline items
            events.forEach((event, index) => {
                const li = document.createElement('li');
                li.className = index === events.length - 1 ? 'ms-4 timeline-item' : 'mb-8 ms-4 timeline-item';
                li.dataset.stepId = event.stepId;
                
                const startTime = Math.floor(event.time / 60) + ':' + String(event.time % 60).padStart(2, '0');
                const duration = Math.floor(event.duration / 60) + ':' + String(event.duration % 60).padStart(2, '0');
                const endTime = Math.floor((event.time + event.duration) / 60) + ':' + String((event.time + event.duration) % 60).padStart(2, '0');
                
                li.innerHTML = `
                    <div class="absolute w-3 h-3 bg-blue-600 rounded-full mt-1.5 -start-1.5 border border-white dark:border-gray-900"></div>
                    <time class="mb-1 text-sm font-normal leading-none text-gray-400 dark:text-gray-500">${startTime}</time>
                    <h3 class="text-lg font-semibold text-blue-800 dark:text-white flex items-center gap-2">
                        <span>▶️</span>
                        <span>${event.stepName}</span>
                        <span class="text-xs font-normal bg-gray-100 text-gray-600 px-2 py-1 rounded-full">Step</span>
                    </h3>
                    <p class="text-sm font-normal text-gray-600 mt-2">${event.description}</p>
                    <div class="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-2">
                        <div class="bg-white border border-gray-200 rounded p-2">
                            <div class="text-xs font-medium text-gray-500 mb-1">Track</div>
                            <div class="text-sm text-gray-800">${event.track}</div>
                        </div>
                        <div class="bg-white border border-gray-200 rounded p-2">
                            <div class="text-xs font-medium text-gray-500 mb-1">Timing</div>
                            <div class="text-xs text-gray-700">
                                <div>${startTime} - ${endTime}</div>
                                <div class="text-gray-500">Duration: ${duration}</div>
                            </div>
                        </div>
                    </div>
                `;
                
                timelineOl.appendChild(li);
            });
            
            itineraryContent.appendChild(timelineOl);
            
            // Add mouseover event listeners for itinerary items
            document.querySelectorAll('.timeline-item').forEach(item => {
                const stepId = item.dataset.stepId;
                const event = events.find(e => e.stepId === stepId);
                
                if (event) {
                    item.addEventListener('mouseenter', function(e) {
                        startTooltipUpdates(event, 'itinerary', e);
                        showTooltip(getItineraryTooltipContent(event), e);
                    });
                    
                    item.addEventListener('mouseleave', function() {
                        hideTooltip();
                    });
                }
            });
        }
        
        // Timeline zoom control functions
        const minScale = 0.01;
        const maxScale = 20.0;
        
        function updateScaleInfo() {
            document.getElementById('scale-info').textContent = 'Scale: ' + currentScale.toFixed(1) + 'x';
            
            // Update button states
            document.getElementById('zoom-out').disabled = currentScale <= minScale + 0.05;
            document.getElementById('zoom-in').disabled = currentScale >= maxScale - 0.05;
        }
        
        function zoomTimelineIn() {
            if (currentScale < maxScale) {
                currentScale = Math.min(maxScale, currentScale * 1.5);
                updateTimeline(currentScale);
                updateScaleInfo();
            }
        }
        
        function zoomTimelineOut() {
            if (currentScale > minScale) {
                currentScale = Math.max(minScale, currentScale / 1.5);
                updateTimeline(currentScale);
                updateScaleInfo();
            }
        }
        
        function setTimelinePreset(minutes) {
            // Calculate scale needed to fit the specified time range in the viewport
            const targetDuration = minutes * 60; // Convert to seconds
            const viewportWidth = timelineWidth - margin.left - margin.right - 100; // Account for margins and scroll
            const pixelsPerSecond = viewportWidth / targetDuration;
            
            // Calculate scale relative to base pixels per second
            const newScale = pixelsPerSecond / basePixelsPerSecond;
            currentScale = Math.max(minScale, Math.min(maxScale, newScale));
            
            updateTimeline(currentScale);
            updateScaleInfo();
        }
        
        // Add event listeners for timeline zoom controls
        document.getElementById('zoom-in').addEventListener('click', zoomTimelineIn);
        document.getElementById('zoom-out').addEventListener('click', zoomTimelineOut);
        
        // Note: Preset buttons are now dynamically generated and managed by settings
        // Event listeners are added in generateCustomPresets() function
        
        // Initialize scale info
        updateScaleInfo();
        
        // Initialize time format controls
        initializeTimeFormatControls();
        
        // Initialize timeline and itinerary
        updateTimeline(currentScale);
        generateItinerary();
        generateResourceUsage();
        
        function getItineraryTooltipContent(event) {
            // Format time
            const formatTime = (seconds) => {
                const minutes = Math.floor(seconds / 60);
                const secs = Math.floor(seconds % 60);
                if (minutes > 0) {
                    return minutes + 'm ' + secs + 's';
                } else {
                    return secs + 's';
                }
            };
            
            let tooltipContent = '<strong>' + event.stepName + '</strong><br/>';
            tooltipContent += 'Track: ' + event.track + '<br/>';
            tooltipContent += 'Start: ' + formatTime(event.time) + '<br/>';
            tooltipContent += 'Duration: ' + formatTime(event.duration) + '<br/>';
            tooltipContent += 'End: ' + formatTime(event.time + event.duration) + '<br/>';
            
            // Add execution status information
            if (executionState === 'running' || executionState === 'paused') {
                tooltipContent += '<br/><strong>Execution Status:</strong><br/>';
                
                if (currentTime < event.time) {
                    const timeUntilStart = event.time - currentTime;
                    tooltipContent += 'Status: <span style="color: #6c757d;">Pending</span><br/>';
                    tooltipContent += 'Starts in: ' + formatTime(timeUntilStart) + '<br/>';
                } else if (currentTime >= event.time && currentTime <= (event.time + event.duration)) {
                    const elapsed = currentTime - event.time;
                    const remaining = (event.time + event.duration) - currentTime;
                    tooltipContent += 'Status: <span style="color: #f39c12;">Active</span><br/>';
                    tooltipContent += 'Elapsed: ' + formatTime(elapsed) + '<br/>';
                    tooltipContent += 'Remaining: ' + formatTime(remaining) + '<br/>';
                    
                    const progress = (elapsed / event.duration) * 100;
                    tooltipContent += 'Progress: ' + progress.toFixed(1) + '%<br/>';
                } else {
                    const completedFor = currentTime - (event.time + event.duration);
                    tooltipContent += 'Status: <span style="color: #28a745;">Completed</span><br/>';
                    tooltipContent += 'Finished: ' + formatTime(completedFor) + ' ago<br/>';
                }
            } else {
                tooltipContent += '<br/><strong>Timeline Position:</strong><br/>';
                tooltipContent += 'Position: ' + formatTime(event.time) + ' - ' + formatTime(event.time + event.duration) + '<br/>';
            }
            
            if (event.task) tooltipContent += '<br/>Task: ' + event.task + '<br/>';
            if (event.description) tooltipContent += '<br/>' + event.description;
            
            return tooltipContent;
        }
        
        function generateResourceUsage() {
            const resourcesContent = document.getElementById('resources-content');
            resourcesContent.innerHTML = '';
            
            // Check if we have resource constraints
            if (!resourceConstraints || resourceConstraints.length === 0) {
                resourcesContent.innerHTML = `
                    <div class="text-center text-gray-500 mt-8">
                        <i class="fas fa-info-circle text-4xl mb-4"></i>
                        <p class="text-lg">No resource constraints defined</p>
                        <p class="text-sm">This program does not specify resource usage limits.</p>
                    </div>
                `;
                return;
            }
            
            // Create resource usage timeline
            createResourceUsageTimeline(resourcesContent);
        }
        
        function createResourceUsageTimeline(container) {
            // Create a timeline showing resource usage over time
            const totalDuration = timelineData.totalDuration || 0;
            
            // Calculate resource usage over time
            const resourceUsageTimeline = calculateResourceUsageOverTime();
            
            // Count warnings (resources with peak usage > 100%)
            let warningCount = 0;
            
            // Create summary cards
            const summaryDiv = document.createElement('div');
            summaryDiv.className = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6';
            
            resourceConstraints.forEach(constraint => {
                const card = document.createElement('div');
                
                const maxUsage = resourceUsageTimeline[constraint.task]?.maxUsage || 0;
                const utilizationPercent = constraint.maxConcurrent > 0 ? 
                    (maxUsage / constraint.maxConcurrent * 100).toFixed(1) : 0;
                
                const isOverutilized = parseFloat(utilizationPercent) > 100;
                if (isOverutilized) {
                    warningCount++;
                }
                
                // Set card styling based on utilization
                if (isOverutilized) {
                    card.className = 'bg-red-50 border-2 border-red-300 rounded-lg p-4 shadow-sm';
                } else if (parseFloat(utilizationPercent) > 80) {
                    card.className = 'bg-yellow-50 border border-yellow-300 rounded-lg p-4 shadow-sm';
                } else {
                    card.className = 'bg-white border border-gray-200 rounded-lg p-4 shadow-sm';
                }
                
                const warningIcon = isOverutilized ? 
                    '<i class="fas fa-exclamation-triangle text-red-600 ml-2" title="Resource overutilized!"></i>' : '';
                
                const utilizationColor = isOverutilized ? 'text-red-600' : 
                    parseFloat(utilizationPercent) > 80 ? 'text-yellow-600' : 'text-blue-600';
                
                card.innerHTML = `
                    <div class="flex items-center justify-between mb-2">
                        <h4 class="font-semibold text-gray-800 flex items-center">
                            ${constraint.task}${warningIcon}
                        </h4>
                        <span class="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded">${constraint.maxConcurrent} max</span>
                    </div>
                    <div class="text-2xl font-bold ${utilizationColor} mb-1">${maxUsage.toFixed(1)}</div>
                    <div class="text-sm text-gray-600 mb-3">Peak Usage</div>
                    <div class="w-full bg-gray-200 rounded-full h-2">
                        <div class="${isOverutilized ? 'bg-red-600' : parseFloat(utilizationPercent) > 80 ? 'bg-yellow-500' : 'bg-blue-600'} h-2 rounded-full" style="width: ${Math.min(utilizationPercent, 100)}%"></div>
                        ${isOverutilized ? `<div class="bg-red-700 h-2 rounded-full relative -top-2 opacity-75" style="width: ${Math.min(utilizationPercent - 100, 50)}%"></div>` : ''}
                    </div>
                    <div class="text-xs ${isOverutilized ? 'text-red-600 font-semibold' : 'text-gray-500'} mt-1">
                        ${utilizationPercent}% utilization
                    </div>
                `;
                
                summaryDiv.appendChild(card);
            });
            
            container.appendChild(summaryDiv);
            
            // Update the resources tab button with warning count
            updateResourcesTabBadge(warningCount);
            
            // Add warning summary if there are overutilized resources OR if we have an optimized state that can be undone
            console.log('Banner check - warningCount:', warningCount, 'originalTimelineData exists:', !!originalTimelineData);
            if (warningCount > 0 || originalTimelineData) {
                const warningBanner = document.createElement('div');
                if (warningCount > 0) {
                    // Show warning banner for conflicts
                    warningBanner.className = 'bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4 flex items-center justify-between';
                    warningBanner.innerHTML = `
                        <div class="flex items-center">
                            <i class="fas fa-exclamation-triangle mr-3"></i>
                            <div>
                                <strong>Resource Warning:</strong> ${warningCount} resource${warningCount > 1 ? 's' : ''} ${warningCount > 1 ? 'are' : 'is'} overutilized. 
                                This may cause delays or conflicts in program execution.
                            </div>
                        </div>
                        <div class="flex gap-2">
                            <button id="resolve-btn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg transition-colors flex items-center">
                                <i class="fas fa-magic mr-2"></i>
                                Resolve
                            </button>
                            <button id="undo-btn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-lg transition-colors flex items-center" style="display: none;">
                                <i class="fas fa-undo mr-2"></i>
                                Undo
                            </button>
                        </div>
                    `;
                } else if (originalTimelineData) {
                    // Show success banner with undo option
                    warningBanner.className = 'bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4 flex items-center justify-between';
                    warningBanner.innerHTML = `
                        <div class="flex items-center">
                            <i class="fas fa-check-circle mr-3"></i>
                            <div>
                                <strong>Optimization Complete:</strong> Resource conflicts have been resolved! 
                                Schedule has been optimized for better resource utilization.
                            </div>
                        </div>
                        <div class="flex gap-2">
                            <button id="undo-btn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-lg transition-colors flex items-center">
                                <i class="fas fa-undo mr-2"></i>
                                Undo
                            </button>
                        </div>
                    `;
                }
                container.insertBefore(warningBanner, summaryDiv);
                
                // Add click handlers for resolve and undo buttons
                setTimeout(() => {
                    const resolveBtn = document.getElementById('resolve-btn');
                    const undoBtn = document.getElementById('undo-btn');
                    
                    if (resolveBtn) {
                        resolveBtn.addEventListener('click', () => {
                            performResolve();
                        });
                    }
                    
                    if (undoBtn) {
                        undoBtn.addEventListener('click', () => {
                            performUndo();
                        });
                    }
                }, 0); // Small delay to ensure DOM is updated
            }
            
            // Create timeline chart
            const chartDiv = document.createElement('div');
            chartDiv.className = 'bg-white border border-gray-200 rounded-lg p-4';
            chartDiv.innerHTML = '<h4 class="font-semibold text-gray-800 mb-4">Resource Usage Timeline</h4>';
            
            const svgContainer = document.createElement('div');
            svgContainer.className = 'w-full h-96 overflow-x-auto';
            
            const svg = d3.select(svgContainer)
                .append('svg')
                .attr('width', Math.max(800, totalDuration * 2))
                .attr('height', 380);
            
            // Draw resource usage charts
            drawResourceUsageChart(svg, resourceUsageTimeline, totalDuration);
            
            chartDiv.appendChild(svgContainer);
            container.appendChild(chartDiv);
        }
        
        function calculateResourceUsageOverTime() {
            const usage = {};
            
            // Initialize usage tracking for each resource constraint
            resourceConstraints.forEach(constraint => {
                usage[constraint.task] = {
                    timeline: [],
                    maxUsage: 0
                };
            });
            
            // Process each step to calculate resource usage
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    // Find matching resource constraints for this step's tasks
                    const stepTasks = step.task ? [step.task] : [];
                    
                    stepTasks.forEach(taskType => {
                        const constraint = resourceConstraints.find(c => c.task === taskType);
                        if (constraint) {
                            // Add usage event
                            usage[constraint.task].timeline.push({
                                time: step.startTime,
                                type: 'start',
                                stepId: step.stepId,
                                stepName: step.name
                            });
                            
                            usage[constraint.task].timeline.push({
                                time: step.endTime,
                                type: 'end',
                                stepId: step.stepId,
                                stepName: step.name
                            });
                        }
                    });
                });
            });
            
            // Calculate peak usage for each resource
            Object.keys(usage).forEach(resourceType => {
                const events = usage[resourceType].timeline.sort((a, b) => {
                    // First sort by time
                    if (a.time !== b.time) return a.time - b.time;
                    // For events at the same time, process 'end' before 'start' 
                    // This prevents false conflicts when one step ends and another begins simultaneously
                    if (a.type === 'end' && b.type === 'start') return -1;
                    if (a.type === 'start' && b.type === 'end') return 1;
                    return 0;
                });
                let currentUsage = 0;
                let maxUsage = 0;
                
                events.forEach(event => {
                    if (event.type === 'start') {
                        currentUsage += 1;
                        maxUsage = Math.max(maxUsage, currentUsage);
                    } else {
                        currentUsage = Math.max(0, currentUsage - 1);
                    }
                });
                
                usage[resourceType].maxUsage = maxUsage;
            });
            
            return usage;
        }
        
        function drawResourceUsageChart(svg, resourceUsageTimeline, totalDuration) {
            const margin = { top: 20, right: 30, bottom: 40, left: 80 };
            const width = +svg.attr('width') - margin.left - margin.right;
            const height = +svg.attr('height') - margin.top - margin.bottom;
            
            const g = svg.append('g')
                .attr('transform', `translate(${margin.left},${margin.top})`);
            
            // Create scales
            const xScale = d3.scaleLinear()
                .domain([0, totalDuration])
                .range([0, width]);
            
            const resourceTypes = Object.keys(resourceUsageTimeline);
            const yScale = d3.scaleBand()
                .domain(resourceTypes)
                .range([0, height])
                .paddingInner(0.1);
            
            // Draw axes
            g.append('g')
                .attr('transform', `translate(0,${height})`)
                .call(d3.axisBottom(xScale)
                    .tickFormat(d => formatTimeForDisplay(d)));
            
            g.append('g')
                .call(d3.axisLeft(yScale));
            
            // Draw resource usage bars for each resource type
            resourceTypes.forEach(resourceType => {
                const resourceData = resourceUsageTimeline[resourceType];
                const constraint = resourceConstraints.find(c => c.task === resourceType);
                const maxConcurrent = constraint ? constraint.maxConcurrent : 1;
                
                // Create usage timeline data
                const events = resourceData.timeline.sort((a, b) => {
                    // First sort by time
                    if (a.time !== b.time) return a.time - b.time;
                    // For events at the same time, process 'end' before 'start' 
                    // This prevents false conflicts when one step ends and another begins simultaneously
                    if (a.type === 'end' && b.type === 'start') return -1;
                    if (a.type === 'start' && b.type === 'end') return 1;
                    return 0;
                });
                const usageData = [];
                let currentUsage = 0;
                
                events.forEach((event, i) => {
                    if (event.type === 'start') {
                        currentUsage += 1;
                    } else {
                        currentUsage = Math.max(0, currentUsage - 1);
                    }
                    
                    // Add data point
                    usageData.push({
                        time: event.time,
                        usage: currentUsage,
                        maxConcurrent: maxConcurrent
                    });
                    
                    // Add next data point if there's a gap
                    if (i < events.length - 1 && events[i + 1].time > event.time) {
                        usageData.push({
                            time: events[i + 1].time,
                            usage: currentUsage,
                            maxConcurrent: maxConcurrent
                        });
                    }
                });
                
                // Draw usage line
                const line = d3.line()
                    .x(d => xScale(d.time))
                    .y(d => yScale(resourceType) + yScale.bandwidth() / 2 - (d.usage / maxConcurrent) * yScale.bandwidth() / 2)
                    .curve(d3.curveStepAfter);
                
                g.append('path')
                    .datum(usageData)
                    .attr('fill', 'none')
                    .attr('stroke', '#4B46B9')
                    .attr('stroke-width', 2)
                    .attr('d', line);
                    
                // Draw capacity line
                g.append('line')
                    .attr('x1', 0)
                    .attr('y1', yScale(resourceType) + yScale.bandwidth() / 2 - yScale.bandwidth() / 2)
                    .attr('x2', width)
                    .attr('y2', yScale(resourceType) + yScale.bandwidth() / 2 - yScale.bandwidth() / 2)
                    .attr('stroke', '#dc2626')
                    .attr('stroke-width', 1)
                    .attr('stroke-dasharray', '3,3')
                    .attr('opacity', 0.7);
            });
            
            // Add legend
            const legend = svg.append('g')
                .attr('transform', `translate(${margin.left + width - 120}, ${margin.top})`);
            
            legend.append('line')
                .attr('x1', 0)
                .attr('y1', 0)
                .attr('x2', 20)
                .attr('y2', 0)
                .attr('stroke', '#4B46B9')
                .attr('stroke-width', 2);
            
            legend.append('text')
                .attr('x', 25)
                .attr('y', 0)
                .attr('dy', '0.35em')
                .style('font-size', '12px')
                .text('Usage');
            
            legend.append('line')
                .attr('x1', 0)
                .attr('y1', 15)
                .attr('x2', 20)
                .attr('y2', 15)
                .attr('stroke', '#dc2626')
                .attr('stroke-width', 1)
                .attr('stroke-dasharray', '3,3');
            
            legend.append('text')
                .attr('x', 25)
                .attr('y', 15)
                .attr('dy', '0.35em')
                .style('font-size', '12px')
                .text('Capacity');
        }
        
        function performResolve() {
            console.log('performResolve called');
            // Store original state for undo
            originalTimelineData = JSON.parse(JSON.stringify(timelineData));
            console.log('Original timeline data stored for undo:', !!originalTimelineData);
            
            // Show loading state
            const btn = document.getElementById('resolve-btn');
            if (!btn) {
                console.error('Resolve button not found');
                return;
            }
            const originalText = btn.innerHTML;
            btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Optimizing...';
            btn.disabled = true;
            
            setTimeout(() => {
                try {
                    // Calculate resource conflicts
                    const conflicts = findResourceConflicts();
                    
                    if (conflicts.length === 0) {
                        showToast('No resource conflicts found to resolve.', 'info');
                        return;
                    }
                    
                    // Apply staggering algorithm
                    const optimizedTimeline = optimizeResourceUsage(conflicts);
                    
                    // Validate the optimized timeline for track overlaps
                    const validationErrors = validateTimelineIntegrity(optimizedTimeline);
                    
                    if (validationErrors.length > 0) {
                        console.warn('Timeline validation errors after optimization:', validationErrors);
                        showToast(`Optimization completed with ${validationErrors.length} validation warning(s). Check console for details.`, 'warning');
                    }
                    
                    // Update timeline data
                    updateTimelineWithOptimizedSchedule(optimizedTimeline);
                    
                    // Refresh all visualizations - this will now show the success banner with undo button
                    console.log('About to refresh views after optimization');
                    refreshAllViews();
                    console.log('Views refreshed, undo button should now be visible');
                    
                    const successMessage = validationErrors.length > 0 ? 
                        `Optimized schedule with ${conflicts.length} conflicts resolved. ${validationErrors.length} validation warnings.` :
                        `Successfully optimized schedule! Resolved ${conflicts.length} resource conflicts.`;
                    
                    showToast(successMessage, validationErrors.length > 0 ? 'warning' : 'success');
                    
                } catch (error) {
                    console.error('Resolve error:', error);
                    showToast('Failed to optimize schedule. Please try again.', 'error');
                } finally {
                    // Restore button state
                    btn.innerHTML = originalText;
                    btn.disabled = false;
                }
            }, 100); // Small delay to show loading state
        }
        
        function performUndo() {
            if (!originalTimelineData) {
                showToast('No changes to undo.', 'info');
                return;
            }
            
            // Show loading state
            const btn = document.getElementById('undo-btn');
            if (!btn) {
                console.error('Undo button not found');
                return;
            }
            const originalText = btn.innerHTML;
            btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Undoing...';
            btn.disabled = true;
            
            setTimeout(() => {
                try {
                    // Restore original timeline data by updating properties
                    const originalData = JSON.parse(JSON.stringify(originalTimelineData));
                    
                    // Update timelineData properties instead of reassigning the const
                    timelineData.tracks = originalData.tracks;
                    timelineData.totalDuration = originalData.totalDuration;
                    if (originalData.programData) {
                        timelineData.programData = originalData.programData;
                    }
                    
                    // Clean up any optimization flags from the restored data
                    timelineData.tracks.forEach(track => {
                        track.steps.forEach(step => {
                            delete step.optimized;
                            delete step.staggerDelay;
                        });
                    });
                    
                    console.log('Timeline data restored, tracks count:', timelineData.tracks.length);
                    console.log('First track steps:', timelineData.tracks[0]?.steps.map(s => ({id: s.stepId, start: s.startTime, optimized: s.optimized})));
                    
                    // Refresh all visualizations
                    refreshAllViews();
                    
                    console.log('All views refreshed after undo');
                    
                    // Hide undo button if it exists
                    const undoBtn = document.getElementById('undo-btn');
                    if (undoBtn) {
                        undoBtn.style.display = 'none';
                    }
                    
                    // Clear stored original data
                    originalTimelineData = null;
                    
                    showToast('Schedule restored to original state.', 'success');
                    
                } catch (error) {
                    console.error('Undo error:', error);
                    showToast('Failed to undo changes. Please refresh the page.', 'error');
                } finally {
                    // Restore button state
                    btn.innerHTML = originalText;
                    btn.disabled = false;
                }
            }, 100); // Small delay to show loading state
        }
        
        function findResourceConflicts() {
            const conflicts = [];
            const resourceUsage = calculateResourceUsageOverTime();
            
            Object.keys(resourceUsage).forEach(resourceType => {
                const constraint = resourceConstraints.find(c => c.task === resourceType);
                if (!constraint) return;
                
                const events = resourceUsage[resourceType].timeline.sort((a, b) => {
                    // First sort by time
                    if (a.time !== b.time) return a.time - b.time;
                    // For events at the same time, process 'end' before 'start' 
                    // This prevents false conflicts when one step ends and another begins simultaneously
                    if (a.type === 'end' && b.type === 'start') return -1;
                    if (a.type === 'start' && b.type === 'end') return 1;
                    return 0;
                });
                let currentUsage = 0;
                let activeSteps = [];
                
                events.forEach(event => {
                    if (event.type === 'start') {
                        currentUsage += 1;
                        activeSteps.push(event.stepId);
                        
                        // Check for overutilization
                        if (currentUsage > constraint.maxConcurrent) {
                            conflicts.push({
                                resourceType: resourceType,
                                time: event.time,
                                overutilization: currentUsage - constraint.maxConcurrent,
                                maxConcurrent: constraint.maxConcurrent,
                                conflictingSteps: [...activeSteps],
                                triggerStep: event.stepId
                            });
                        }
                    } else {
                        currentUsage = Math.max(0, currentUsage - 1);
                        activeSteps = activeSteps.filter(id => id !== event.stepId);
                    }
                });
            });
            
            return conflicts;
        }
        
        function optimizeResourceUsage(conflicts) {
            console.log('RESOLVE: Starting optimization...');
            console.log('Initial conflicts detected:', conflicts.length);
            conflicts.forEach((conflict, index) => {
                console.log(`   ${index + 1}. ${conflict.resourceType}: ${conflict.overutilization + conflict.maxConcurrent}/${conflict.maxConcurrent} (${conflict.conflictingSteps.join(', ')})`);
            });
            
            const optimizedSteps = {};
            
            // Create a copy of timeline data to modify with track information
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    optimizedSteps[step.stepId] = {
                        ...step,
                        originalStartTime: step.startTime,
                        staggerDelay: 0,
                        trackId: track.trackId,
                        trackName: track.name
                    };
                });
            });
            
            // Build a comprehensive resource availability timeline
            const resourceTimeline = buildResourceAvailabilityTimeline(optimizedSteps);
            console.log('Resource timeline initialized for:', Object.keys(resourceTimeline));
            
            // Sort all steps by their original start time for efficient scheduling
            // Sort steps by original start time AND dependency order to preserve track ordering
            const allSteps = Object.values(optimizedSteps).sort((a, b) => {
                // First, sort by original start time
                if (a.originalStartTime !== b.originalStartTime) {
                    return a.originalStartTime - b.originalStartTime;
                }
                // For steps starting at the same time, prioritize dependencies
                const aHasDep = a.startTrigger && (a.startTrigger.type === 'afterStep' || a.startTrigger.type === 'afterStepWithBuffer');
                const bHasDep = b.startTrigger && (b.startTrigger.type === 'afterStep' || b.startTrigger.type === 'afterStepWithBuffer');
                
                if (aHasDep && !bHasDep) return 1; // a depends on something, process after b
                if (!aHasDep && bHasDep) return -1; // b depends on something, process after a
                
                return 0; // Same priority
            });
            
            console.log(`Processing ${allSteps.length} steps in dependency-aware order...`);
            
            // Schedule each step at the earliest possible time
            allSteps.forEach((step, index) => {
                console.log(`\\nStep ${index + 1}/${allSteps.length}: "${step.name}" (${step.stepId})`);
                console.log(`   Original time: ${step.originalStartTime}s, Duration: ${step.duration}s, Resource: ${step.task || 'none'}`);
                
                const earliestStartTime = findEarliestAvailableSlot(step, optimizedSteps, resourceTimeline);
                console.log(`   Earliest available slot: ${earliestStartTime}s`);
                
                if (earliestStartTime > step.originalStartTime) {
                    step.staggerDelay = earliestStartTime - step.originalStartTime;
                    step.startTime = earliestStartTime;
                    step.endTime = earliestStartTime + step.duration;
                    step.optimized = true; // Mark as optimized since we changed it
                    console.log(`   DELAYED: ${step.staggerDelay}s delay applied (${step.originalStartTime}s to ${earliestStartTime}s)`);
                } else {
                    // Step can start at original time
                    step.startTime = step.originalStartTime;
                    step.endTime = step.originalStartTime + step.duration;
                    step.staggerDelay = 0;
                    console.log(`   NO DELAY: Step can start at original time`);
                }
                
                // SANITY CHECK: Validate afterStep dependencies are not violated
                if (step.startTrigger && (step.startTrigger.type === 'afterStep' || step.startTrigger.type === 'afterStepWithBuffer')) {
                    const referencedStepId = step.startTrigger.stepId;
                    const referencedStep = optimizedSteps[referencedStepId];
                    if (referencedStep) {
                        const refEndTime = (referencedStep.startTime || referencedStep.originalStartTime) + referencedStep.duration;
                        const offsetSeconds = step.startTrigger.offsetSeconds || 0;
                        const requiredStartTime = refEndTime + offsetSeconds;
                        
                        if (step.startTime < requiredStartTime) {
                            console.error(`DEPENDENCY VIOLATION: Step ${step.stepId} (${step.title}) scheduled at ${step.startTime}s but must start after ${requiredStartTime}s due to dependency on ${referencedStepId}`);
                            // Force correct timing
                            step.startTime = requiredStartTime;
                            step.endTime = requiredStartTime + step.duration;
                            step.staggerDelay = requiredStartTime - step.originalStartTime;
                            step.optimized = true;
                        }
                    }
                }
                
                // Update resource timeline with this step's resource usage
                updateResourceTimeline(step, resourceTimeline);
            });
            
            // Validate and fix any remaining track overlaps
            validateAndFixTrackOverlaps(optimizedSteps);
            
            // FINAL VALIDATION: Check entire schedule for dependency violations
            const violations = validateDependencyOrder(optimizedSteps);
            if (violations.length > 0) {
                console.error('DEPENDENCY VIOLATIONS DETECTED:', violations);
                showToast('warning', `Schedule has ${violations.length} dependency violations! Check console for details.`);
            }
            
            console.log('RESOLVE: Optimization completed successfully!');
            const optimizedCount = Object.values(optimizedSteps).filter(s => s.optimized).length;
            console.log(`Summary: ${optimizedCount} steps were moved to resolve conflicts`);
            
            // POST-OPTIMIZATION: Re-check for conflicts with the optimized schedule
            console.log('\\nPOST-OPTIMIZATION ANALYSIS:');
            const postOptimizationConflicts = detectPostOptimizationConflicts(optimizedSteps);
            
            if (postOptimizationConflicts.length === 0) {
                console.log('All resource conflicts successfully resolved!');
            } else {
                console.log(`${postOptimizationConflicts.length} conflicts still remain:`);
                postOptimizationConflicts.forEach((conflict, index) => {
                    console.log(`   ${index + 1}. ${conflict.resourceType}: ${conflict.overutilization + conflict.maxConcurrent}/${conflict.maxConcurrent} at ${conflict.time}s`);
                    console.log(`      Conflicting steps: ${conflict.conflictingSteps.join(', ')}`);
                    
                    // Analyze why these steps couldn't be moved
                    conflict.conflictingSteps.forEach(stepId => {
                        const step = optimizedSteps[stepId];
                        if (step) {
                            const wasOptimized = step.optimized ? 'YES' : 'NO';
                            const delay = step.staggerDelay || 0;
                            console.log(`         ${stepId}: Optimized=${wasOptimized}, Delay=${delay}s, Start=${step.startTime || step.originalStartTime}s`);
                            
                            // Check dependencies
                            if (step.startTrigger && (step.startTrigger.type === 'afterStep' || step.startTrigger.type === 'afterStepWithBuffer')) {
                                console.log(`            Depends on: ${step.startTrigger.stepId}`);
                            }
                        }
                    });
                });
                
                // Try a more aggressive secondary optimization pass
                console.log('');
                console.log('=== ATTEMPTING AGGRESSIVE SECONDARY OPTIMIZATION ===');
                postOptimizationConflicts.forEach(conflict => {
                    console.log(`Trying to resolve conflict: ${conflict.resourceType} at ${conflict.time}s`);
                    
                    // Sort conflicting steps by flexibility (fewer dependents = more flexible)
                    const conflictingStepData = conflict.conflictingSteps.map(stepId => {
                        const step = optimizedSteps[stepId];
                        const dependents = findDependentSteps(stepId);
                        return {
                            stepId,
                            step,
                            dependentCount: dependents.length,
                            currentStart: step.startTime || step.originalStartTime
                        };
                    }).sort((a, b) => a.dependentCount - b.dependentCount);
                    
                    // Try to move the most flexible steps further back
                    for (let i = 1; i < conflictingStepData.length; i++) {
                        const stepData = conflictingStepData[i];
                        const step = stepData.step;
                        const resource = step.task;
                        const resourceInfo = resourceTimeline[resource];
                        
                        if (resourceInfo) {
                            console.log(`  Attempting aggressive move of ${stepData.stepId}...`);
                            
                            // Try to find a slot much later to avoid the conflict
                            const conflictEnd = conflict.time + 600; // Look for slots 10 minutes after conflict
                            const newStartTime = findEarliestAvailableSlot(step, resource, resourceInfo, conflictEnd);
                            
                            if (newStartTime > stepData.currentStart) {
                                const additionalDelay = newStartTime - step.originalStartTime;
                                step.staggerDelay = additionalDelay;
                                step.startTime = newStartTime;
                                step.endTime = newStartTime + step.duration;
                                step.optimized = true;
                                
                                // Update resource timeline
                                updateResourceTimeline(step, resourceTimeline);
                                
                                console.log(`    Moved ${stepData.stepId} to ${newStartTime}s (delay: ${additionalDelay}s)`);
                            }
                        }
                    }
                });
                
                // Check conflicts again after aggressive optimization  
                const finalConflicts = findResourceConflicts(optimizedSteps);
                console.log('');
                console.log('=== FINAL CONFLICT CHECK ===');
                if (finalConflicts.length === 0) {
                    console.log('SUCCESS: All conflicts resolved after aggressive optimization!');
                } else {
                    console.log(`${finalConflicts.length} conflicts still remain after aggressive optimization.`);
                }
            }
            
            // Calculate and report overutilization periods and their total duration
            const overutilizationAnalysis = analyzeOverutilizationDuration(optimizedSteps);
            console.log(`Overutilization analysis:`);
            console.log(`   Total overutilized periods: ${overutilizationAnalysis.periods}`);
            console.log(`   Total overutilization duration: ${overutilizationAnalysis.totalDuration}s`);
            if (overutilizationAnalysis.byResource) {
                Object.keys(overutilizationAnalysis.byResource).forEach(resource => {
                    const resourceData = overutilizationAnalysis.byResource[resource];
                    console.log(`   ${resource}: ${resourceData.periods} periods, ${resourceData.duration}s total`);
                });
            }
            
            return optimizedSteps;
        }
        
        function detectPostOptimizationConflicts(optimizedSteps) {
            /**
             * Detect conflicts in the optimized schedule using the same logic as findResourceConflicts
             */
            const conflicts = [];
            const resourceUsage = calculateOptimizedResourceUsage(optimizedSteps);
            
            Object.keys(resourceUsage).forEach(resourceType => {
                const constraint = resourceConstraints.find(c => c.task === resourceType);
                if (!constraint) return;
                
                const events = resourceUsage[resourceType].timeline.sort((a, b) => {
                    // First sort by time
                    if (a.time !== b.time) return a.time - b.time;
                    // For events at the same time, process 'end' before 'start' 
                    // This prevents false conflicts when one step ends and another begins simultaneously
                    if (a.type === 'end' && b.type === 'start') return -1;
                    if (a.type === 'start' && b.type === 'end') return 1;
                    return 0;
                });
                let currentUsage = 0;
                let activeSteps = [];
                
                events.forEach(event => {
                    if (event.type === 'start') {
                        currentUsage += 1;
                        activeSteps.push(event.stepId);
                        
                        // Check for overutilization
                        if (currentUsage > constraint.maxConcurrent) {
                            conflicts.push({
                                resourceType: resourceType,
                                time: event.time,
                                overutilization: currentUsage - constraint.maxConcurrent,
                                maxConcurrent: constraint.maxConcurrent,
                                conflictingSteps: [...activeSteps],
                                triggerStep: event.stepId
                            });
                        }
                    } else {
                        currentUsage = Math.max(0, currentUsage - 1);
                        activeSteps = activeSteps.filter(id => id !== event.stepId);
                    }
                });
            });
            
            return conflicts;
        }
        
        function calculateOptimizedResourceUsage(optimizedSteps) {
            /**
             * Calculate resource usage timeline for optimized steps
             */
            const resourceUsage = {};
            
            // Initialize resource usage tracking
            resourceConstraints.forEach(constraint => {
                resourceUsage[constraint.task] = {
                    timeline: []
                };
            });
            
            // Add events for each optimized step
            Object.values(optimizedSteps).forEach(step => {
                if (step.task && resourceUsage[step.task]) {
                    const startTime = step.startTime || step.originalStartTime;
                    const endTime = startTime + step.duration;
                    
                    resourceUsage[step.task].timeline.push(
                        { type: 'start', time: startTime, stepId: step.stepId },
                        { type: 'end', time: endTime, stepId: step.stepId }
                    );
                }
            });
            
            return resourceUsage;
        }
        
        function analyzeOverutilizationDuration(optimizedSteps) {
            /**
             * Analyze the duration and periods of overutilization in the optimized schedule
             */
            const analysis = {
                periods: 0,
                totalDuration: 0,
                byResource: {}
            };
            
            const timeStep = 1; // Check every second for precise measurement
            const resourceUsage = {};
            
            // Initialize resource usage tracking
            resourceConstraints.forEach(constraint => {
                resourceUsage[constraint.task] = {
                    maxConcurrent: constraint.maxConcurrent,
                    timeline: []
                };
                analysis.byResource[constraint.task] = {
                    periods: 0,
                    duration: 0
                };
            });
            
            // Build timeline for optimized steps
            Object.values(optimizedSteps).forEach(step => {
                if (step.task && resourceUsage[step.task]) {
                    const startTime = step.startTime || step.originalStartTime;
                    const endTime = startTime + step.duration;
                    
                    resourceUsage[step.task].timeline.push({
                        startTime: startTime,
                        endTime: endTime,
                        stepId: step.stepId,
                        stepName: step.name
                    });
                }
            });
            
            // Find maximum time to check
            let maxTime = 0;
            Object.values(optimizedSteps).forEach(step => {
                const endTime = (step.startTime || step.originalStartTime) + step.duration;
                maxTime = Math.max(maxTime, endTime);
            });
            
            // Track overutilization periods
            let currentlyOverutilized = false;
            let overutilizationStart = 0;
            
            for (let time = 0; time <= maxTime; time += timeStep) {
                let hasOverutilization = false;
                
                Object.keys(resourceUsage).forEach(resourceType => {
                    const resource = resourceUsage[resourceType];
                    let concurrentUsage = 0;
                    
                    resource.timeline.forEach(usage => {
                        // Treat steps as ending 0.001 seconds early to avoid edge case overlaps
                        const effectiveEndTime = usage.endTime - 0.001;
                        if (time >= usage.startTime && time < effectiveEndTime) {
                            concurrentUsage++;
                        }
                    });
                    
                    if (concurrentUsage > resource.maxConcurrent) {
                        hasOverutilization = true;
                        
                        if (!currentlyOverutilized) {
                            // Starting a new overutilization period
                            analysis.periods++;
                            analysis.byResource[resourceType].periods++;
                            overutilizationStart = time;
                            currentlyOverutilized = true;
                        }
                    }
                });
                
                if (currentlyOverutilized && !hasOverutilization) {
                    // Ending an overutilization period
                    const periodDuration = time - overutilizationStart;
                    analysis.totalDuration += periodDuration;
                    
                    // Attribute to all overutilized resources (simplification)
                    Object.keys(analysis.byResource).forEach(resource => {
                        analysis.byResource[resource].duration += periodDuration;
                    });
                    
                    currentlyOverutilized = false;
                }
            }
            
            // Handle case where overutilization continues to the end
            if (currentlyOverutilized) {
                const periodDuration = maxTime - overutilizationStart;
                analysis.totalDuration += periodDuration;
                Object.keys(analysis.byResource).forEach(resource => {
                    analysis.byResource[resource].duration += periodDuration;
                });
            }
            
            return analysis;
        }
        
        function buildResourceAvailabilityTimeline(optimizedSteps) {
            /**
             * Build a timeline showing when each resource is available/occupied
             */
            const timeline = {};
            
            // Initialize timeline for each resource constraint
            resourceConstraints.forEach(constraint => {
                timeline[constraint.task] = {
                    maxConcurrent: constraint.maxConcurrent,
                    usage: [] // Array of {start, end, stepId} objects
                };
            });
            
            return timeline;
        }
        
        function findEarliestAvailableSlot(step, optimizedSteps, resourceTimeline) {
            /**
             * Find the earliest time when this step can start without violating constraints
             * CRITICAL: Dependencies must ALWAYS be respected regardless of resource availability
             */
            const resource = step.task;
            const earliestDependencyTime = calculateEarliestDependencyTime(step, optimizedSteps);
            const earliestTrackTime = calculateEarliestTrackSlot(step, optimizedSteps);
            
            console.log(`     Constraint analysis for "${step.name}":`);
            console.log(`        Original start: ${step.originalStartTime}s`);
            console.log(`        Dependency constraint: ${earliestDependencyTime}s`);
            console.log(`        Track constraint: ${earliestTrackTime}s`);
            
            // ABSOLUTE MINIMUM start time (cannot start before this under any circumstances)
            const absoluteMinimum = Math.max(step.originalStartTime, earliestDependencyTime, earliestTrackTime);
            console.log(`        Absolute minimum: ${absoluteMinimum}s`);
            console.log(`        Resource: ${resource || 'none'}`);
            
            if (resource && resourceTimeline[resource]) {
                console.log(`        Current resource usage slots:`, resourceTimeline[resource].usage.length);
            }
            
            // If no resource constraint, start at absolute minimum
            if (!resource || !resourceTimeline[resource]) {
                return absoluteMinimum;
            }
            
            const resourceInfo = resourceTimeline[resource];
            
            // First, check if the step can start at its absolute minimum time
            const minimumEnd = absoluteMinimum + step.duration;
            if (isSlotAvailable(absoluteMinimum, minimumEnd, resource, resourceInfo)) {
                return absoluteMinimum; // Can start at minimum required time
            }
            
            // If not, find the earliest slot AFTER the absolute minimum where this step can fit
            console.log(`        Searching for available slot starting from ${absoluteMinimum}s...`);
            for (let candidateStart = absoluteMinimum; candidateStart < absoluteMinimum + 3600; candidateStart += 30) {
                const candidateEnd = candidateStart + step.duration;
                
                if (isSlotAvailable(candidateStart, candidateEnd, resource, resourceInfo)) {
                    console.log(`        Found available slot at ${candidateStart}s`);
                    return candidateStart;
                } else {
                    // Debug why this slot is not available
                    let concurrentUsage = 0;
                    resourceInfo.usage.forEach(usage => {
                        const effectiveUsageEnd = usage.end - 0.001;
                        const effectiveProposedEnd = candidateEnd - 0.001;
                        if (candidateStart < effectiveUsageEnd && effectiveProposedEnd > usage.start) {
                            concurrentUsage++;
                        }
                    });
                    if (candidateStart % 120 === 0) { // Log every 2 minutes to avoid spam
                        console.log(`          Slot ${candidateStart}-${candidateEnd}s: ${concurrentUsage}/${resourceInfo.maxConcurrent} usage`);
                    }
                }
            }
            console.log(`        WARNING: No available slot found within 1 hour window!`);
            
            // Fallback: place at the end of all current usage, but not before absolute minimum
            const latestEnd = Math.max(0, ...resourceInfo.usage.map(u => u.end));
            return Math.max(absoluteMinimum, latestEnd);
        }
        
        function calculateEarliestDependencyTime(step, optimizedSteps) {
            /**
             * Calculate earliest time based on step dependencies
             * CRITICAL: afterStep dependencies must be strictly enforced
             */
            const startTrigger = step.startTrigger || {};
            
            if (startTrigger.type === 'afterStep' || startTrigger.type === 'afterStepWithBuffer') {
                const referencedStepId = startTrigger.stepId;
                const offsetSeconds = startTrigger.offsetSeconds || 0;
                const event = startTrigger.event || 'end';
                
                const referencedStep = optimizedSteps[referencedStepId];
                if (referencedStep) {
                    // Use the current scheduled time for the referenced step
                    const refStart = referencedStep.startTime || referencedStep.originalStartTime;
                    const refTime = event === 'start' ? refStart : refStart + referencedStep.duration;
                    const dependencyTime = Math.max(0, refTime + offsetSeconds);
                    
                    // For afterStep triggers, this is a hard constraint - step CANNOT start before this time
                    return Math.max(step.originalStartTime, dependencyTime);
                }
            } else if (startTrigger.type === 'programStart') {
                const offsetSeconds = startTrigger.offsetSeconds || 0;
                return Math.max(0, offsetSeconds);
            }
            
            return step.originalStartTime;
        }
        
        function calculateEarliestTrackSlot(step, optimizedSteps) {
            /**
             * Calculate earliest time to avoid overlaps within the same track
             * CRITICAL: Maintain the original order of steps within tracks
             */
            const trackSteps = Object.values(optimizedSteps).filter(s => 
                s.trackId === step.trackId && s.stepId !== step.stepId
            );
            
            let earliestTime = step.originalStartTime;
            
            trackSteps.forEach(otherStep => {
                const otherStart = otherStep.startTime || otherStep.originalStartTime;
                const otherEnd = otherStart + otherStep.duration;
                
                // If this step originally started after the other step, maintain that order
                if (step.originalStartTime >= otherStep.originalStartTime) {
                    // This step should come after the other step - allow it to start exactly when other ends
                    earliestTime = Math.max(earliestTime, otherEnd);
                }
                // If this step originally started before the other step, maintain that order
                // (don't push it past the other step's start time)
            });
            
            return earliestTime;
        }
        
        function isSlotAvailable(startTime, endTime, resource, resourceInfo) {
            /**
             * Check if a time slot is available for a resource
             */
            let concurrentUsage = 0;
            
            resourceInfo.usage.forEach(usage => {
                // Check if this usage overlaps with our proposed time slot
                // Treat existing usage as ending 0.001 seconds early to avoid edge case overlaps
                const effectiveUsageEnd = usage.end - 0.001;
                const effectiveProposedEnd = endTime - 0.001;
                if (startTime < effectiveUsageEnd && effectiveProposedEnd > usage.start) {
                    concurrentUsage++;
                }
            });
            
            return concurrentUsage < resourceInfo.maxConcurrent;
        }
        
        function updateResourceTimeline(step, resourceTimeline) {
            /**
             * Add this step's resource usage to the timeline
             */
            const resource = step.task;
            if (resource && resourceTimeline[resource]) {
                const startTime = step.originalStartTime + (step.staggerDelay || 0);
                const endTime = startTime + step.duration;
                
                resourceTimeline[resource].usage.push({
                    start: startTime,
                    end: endTime,
                    stepId: step.stepId,
                    stepName: step.name
                });
                
                // Sort usage by start time for efficient lookup
                resourceTimeline[resource].usage.sort((a, b) => a.start - b.start);
            }
        }
        
        function validateAndFixTrackOverlaps(optimizedSteps) {
            // Group steps by track
            const stepsByTrack = {};
            Object.values(optimizedSteps).forEach(step => {
                if (!stepsByTrack[step.trackId]) {
                    stepsByTrack[step.trackId] = [];
                }
                stepsByTrack[step.trackId].push(step);
            });
            
            // Validate each track for overlaps
            Object.keys(stepsByTrack).forEach(trackId => {
                const trackSteps = stepsByTrack[trackId];
                
                // Sort steps by start time
                trackSteps.sort((a, b) => {
                    const aStart = a.originalStartTime + a.staggerDelay;
                    const bStart = b.originalStartTime + b.staggerDelay;
                    return aStart - bStart;
                });
                
                // Fix any overlaps by adjusting subsequent steps
                for (let i = 1; i < trackSteps.length; i++) {
                    const currentStep = trackSteps[i];
                    const previousStep = trackSteps[i - 1];
                    
                    const currentStart = currentStep.originalStartTime + currentStep.staggerDelay;
                    const previousEnd = previousStep.originalStartTime + previousStep.staggerDelay + previousStep.duration;
                    
                    // If there's an overlap, adjust the current step
                    if (currentStart < previousEnd) {
                        const requiredDelay = previousEnd - currentStep.originalStartTime; // No buffer needed
                        currentStep.staggerDelay = Math.max(currentStep.staggerDelay, requiredDelay);
                        
                        // Mark as optimized since we've adjusted it
                        currentStep.optimized = true;
                    }
                }
            });
        }
        
        function validateDependencyOrder(optimizedSteps) {
            /**
             * Validate that all afterStep dependencies are respected in the schedule
             */
            const violations = [];
            
            Object.values(optimizedSteps).forEach(step => {
                if (step.startTrigger && (step.startTrigger.type === 'afterStep' || step.startTrigger.type === 'afterStepWithBuffer')) {
                    const referencedStepId = step.startTrigger.stepId;
                    const referencedStep = optimizedSteps[referencedStepId];
                    
                    if (referencedStep) {
                        const stepStart = step.startTime || step.originalStartTime;
                        const refEnd = (referencedStep.startTime || referencedStep.originalStartTime) + referencedStep.duration;
                        const offsetSeconds = step.startTrigger.offsetSeconds || 0;
                        const requiredStart = refEnd + offsetSeconds;
                        
                        if (stepStart < requiredStart) {
                            violations.push({
                                stepId: step.stepId,
                                stepTitle: step.title,
                                referencedStepId: referencedStepId,
                                referencedStepTitle: referencedStep.title,
                                actualStart: stepStart,
                                requiredStart: requiredStart,
                                violation: `${step.title} starts at ${stepStart}s but must start after ${requiredStart}s`
                            });
                        }
                    }
                }
            });
            
            return violations;
        }
        
        function validateTimelineIntegrity(optimizedSteps) {
            /**
             * Validate the optimized timeline for track overlaps and other integrity issues.
             * 
             * @param {Object} optimizedSteps - The optimized steps data
             * @returns {Array} Array of validation error messages
             */
            const errors = [];
            
            // Group steps by track
            const stepsByTrack = {};
            Object.values(optimizedSteps).forEach(step => {
                if (!stepsByTrack[step.trackId]) {
                    stepsByTrack[step.trackId] = [];
                }
                stepsByTrack[step.trackId].push(step);
            });
            
            // Validate each track for overlaps
            Object.keys(stepsByTrack).forEach(trackId => {
                const trackSteps = stepsByTrack[trackId];
                
                // Sort steps by start time
                trackSteps.sort((a, b) => {
                    const aStart = a.originalStartTime + a.staggerDelay;
                    const bStart = b.originalStartTime + b.staggerDelay;
                    return aStart - bStart;
                });
                
                // Check for overlaps
                for (let i = 1; i < trackSteps.length; i++) {
                    const currentStep = trackSteps[i];
                    const previousStep = trackSteps[i - 1];
                    
                    const currentStart = currentStep.originalStartTime + currentStep.staggerDelay;
                    const previousEnd = previousStep.originalStartTime + previousStep.staggerDelay + previousStep.duration;
                    
                    // If there's an overlap, record the error
                    if (currentStart < previousEnd) {
                        const overlapDuration = previousEnd - currentStart;
                        errors.push(
                            `Track '${currentStep.trackName || trackId}': Steps '${previousStep.name}' and '${currentStep.name}' ` +
                            `overlap by ${overlapDuration} seconds after optimization.`
                        );
                    }
                }
            });
            
            // Validate timing consistency
            Object.values(optimizedSteps).forEach(step => {
                const calculatedStartTime = step.originalStartTime + step.staggerDelay;
                if (Math.abs(step.startTime - calculatedStartTime) > 1) { // Allow 1 second tolerance
                    errors.push(
                        `Step '${step.name}': Inconsistent timing. Expected start: ${calculatedStartTime}s, ` +
                        `actual start: ${step.startTime}s`
                    );
                }
                
                if (step.endTime !== step.startTime + step.duration) {
                    errors.push(
                        `Step '${step.name}': Inconsistent end time. Expected: ${step.startTime + step.duration}s, ` +
                        `actual: ${step.endTime}s`
                    );
                }
            });
            
            return errors;
        }
        
        function findDependentSteps(stepId) {
            // Find all steps that depend on this step
            const dependents = [];
            edges.forEach(edge => {
                if (edge.source === stepId) {
                    dependents.push(edge.target);
                    // Recursively find dependents of dependents
                    dependents.push(...findDependentSteps(edge.target));
                }
            });
            return [...new Set(dependents)]; // Remove duplicates
        }
        
        function updateTimelineWithOptimizedSchedule(optimizedSteps) {
            // Update the timeline data with optimized timing
            timelineData.tracks.forEach(track => {
                track.steps.forEach((step, index) => {
                    const optimizedStep = optimizedSteps[step.stepId];
                    if (optimizedStep && optimizedStep.staggerDelay > 0) {
                        track.steps[index] = {
                            ...step,
                            startTime: optimizedStep.startTime,
                            endTime: optimizedStep.endTime,
                            optimized: true,
                            staggerDelay: optimizedStep.staggerDelay
                        };
                    }
                });
            });
            
            // Recalculate total duration
            let maxEndTime = 0;
            timelineData.tracks.forEach(track => {
                track.steps.forEach(step => {
                    maxEndTime = Math.max(maxEndTime, step.endTime);
                });
            });
            timelineData.totalDuration = maxEndTime;
            
            // Update the duration display
            updateDurationDisplay();
        }
        
        function refreshAllViews() {
            // Force refresh all views regardless of visibility state
            // This ensures optimized data is reflected everywhere
            
            // Refresh timeline view
            try {
                updateTimeline(currentScale);
            } catch (e) {
                console.warn('Timeline refresh failed:', e);
            }
            
            // Refresh DAG view
            try {
                renderDAG();
            } catch (e) {
                console.warn('DAG refresh failed:', e);
            }
            
            // Refresh resources view
            try {
                generateResourceUsage();
            } catch (e) {
                console.warn('Resources refresh failed:', e);
            }
            
            // Refresh itinerary view
            try {
                generateItinerary();
            } catch (e) {
                console.warn('Itinerary refresh failed:', e);
            }
        }
        
        function showToast(message, type = 'info') {
            // Create toast notification
            const toast = document.createElement('div');
            toast.className = `fixed top-4 right-4 z-50 px-6 py-3 rounded-lg shadow-lg transition-all duration-300 ${
                type === 'success' ? 'bg-green-600 text-white' :
                type === 'error' ? 'bg-red-600 text-white' :
                type === 'warning' ? 'bg-yellow-600 text-white' :
                'bg-blue-600 text-white'
            }`;
            
            const icon = type === 'success' ? 'fas fa-check-circle' :
                        type === 'error' ? 'fas fa-exclamation-circle' :
                        type === 'warning' ? 'fas fa-exclamation-triangle' :
                        'fas fa-info-circle';
            
            toast.innerHTML = `
                <div class="flex items-center">
                    <i class="${icon} mr-2"></i>
                    <span>${message}</span>
                </div>
            `;
            
            document.body.appendChild(toast);
            
            // Animate in
            setTimeout(() => {
                toast.style.transform = 'translateX(0)';
                toast.style.opacity = '1';
            }, 10);
            
            // Auto-remove after 4 seconds
            setTimeout(() => {
                toast.style.transform = 'translateX(100%)';
                toast.style.opacity = '0';
                setTimeout(() => {
                    if (toast.parentNode) {
                        toast.parentNode.removeChild(toast);
                    }
                }, 300);
            }, 4000);
        }

        function updateResourcesTabBadge(warningCount) {
            const badge = document.querySelector('#view-resources .resource-warning-badge');
            if (badge) {
                if (warningCount > 0) {
                    badge.textContent = warningCount;
                    badge.classList.remove('hidden');
                } else {
                    badge.classList.add('hidden');
                }
            }
        }
    </script>
</body>
</html>"""
    
    return html_template

def generate_dag_visualization(program_file: str, output_file: str = None, open_browser: bool = True) -> str:
    """
    Generate a DAG visualization from a program file.
    
    Args:
        program_file: Path to the program JSON file
        output_file: Output HTML file path (optional)
        open_browser: Whether to open the result in a browser
        
    Returns:
        Path to the generated HTML file
    """
    # Load program
    if not os.path.exists(program_file):
        raise FileNotFoundError(f"Program file not found: {program_file}")
    
    program = load_program_file(program_file)
    program_name = program.get('name', os.path.basename(program_file))
    program_description = program.get('description', '')
    
    # Load environment and resource constraints if available
    environment_data = None
    resource_constraints = []
    
    try:
        from .environment_loader import load_resource_constraints, get_default_loader
        
        # Load resource constraints (handles both embedded and environment-based)
        resource_constraints = load_resource_constraints(program)
        
        # Load full environment data if environment is specified
        environment_id = program.get('environment')
        if environment_id:
            loader = get_default_loader()
            try:
                environment_data = loader.get_environment(environment_id)
            except (FileNotFoundError, ValueError):
                print(f"Warning: Could not load environment '{environment_id}'")
                
    except ImportError:
        print("Warning: Environment loader not available, using embedded constraints only")
        resource_constraints = program.get('resourceConstraints', [])
    
    # Extract dependencies
    nodes, edges = extract_step_dependencies(program)
    
    # Generate HTML with environment data
    html_content = generate_dag_html(nodes, edges, program, environment_data, resource_constraints)
    
    # Determine output file
    if output_file is None:
        program_path = Path(program_file)
        output_file = program_path.parent / f"{program_path.stem}_dag.html"
    
    # Write HTML file
    with open(output_file, 'w', encoding='utf-8') as f:
        f.write(html_content)
    
    print(f"DAG visualization generated: {output_file}")
    print(f"Found {len(nodes)} steps and {len(edges)} dependencies")
    if resource_constraints:
        print(f"Found {len(resource_constraints)} resource constraints")
    if environment_data:
        print(f"Using environment: {environment_data.get('name', environment_id)}")
    
    # Open in browser
    if open_browser:
        webbrowser.open(f"file://{os.path.abspath(output_file)}")
    
    return str(output_file)

def main():
    """Command-line interface for DAG visualization."""
    parser = argparse.ArgumentParser(description="Generate DAG visualization from Rhylthyme program files")
    parser.add_argument("program_file", help="Path to the program JSON file")
    parser.add_argument("-o", "--output", help="Output HTML file path")
    parser.add_argument("--no-browser", action="store_true", help="Don't open browser automatically")
    
    args = parser.parse_args()
    
    try:
        output_file = generate_dag_visualization(
            args.program_file, 
            args.output, 
            not args.no_browser
        )
        print(f"Success! DAG visualization saved to: {output_file}")
    except Exception as e:
        print(f"Error: {e}")
        return 1
    
    return 0

if __name__ == "__main__":
    exit(main()) 