{% extends "base.html" %}

{% block title %}Run {{ run.run_id[:12] }} - Art-DAG L1{% endblock %}

{% block head %}
{{ super() }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.23.0/cytoscape.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cytoscape-dagre@2.5.0/cytoscape-dagre.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.4.12/dist/hls.min.js"></script>
{% endblock %}

{% block content %}
{% set status_colors = {'completed': 'green', 'running': 'blue', 'pending': 'yellow', 'failed': 'red', 'paused': 'yellow'} %}
{% set color = status_colors.get(run.status, 'gray') %}

<div class="max-w-6xl mx-auto">
    <!-- Header -->
    <div class="flex items-center space-x-4 mb-6">
        <a href="/runs" class="text-gray-400 hover:text-white">&larr; Runs</a>
        <h1 class="text-2xl font-bold font-mono">{{ run.run_id[:16] }}...</h1>
        <span class="bg-{{ color }}-900 text-{{ color }}-300 px-3 py-1 rounded text-sm uppercase">
            {{ run.status }}
        </span>
        {% if run.cached %}
        <span class="bg-purple-900 text-purple-300 px-3 py-1 rounded text-sm">Cached</span>
        {% endif %}
        {% if run.error %}
        <span class="text-red-400 text-sm ml-2">{{ run.error }}</span>
        {% endif %}
        {% if run.checkpoint_frame %}
        <span class="text-gray-400 text-sm ml-2">
            Checkpoint: {{ run.checkpoint_frame }}{% if run.total_frames %} / {{ run.total_frames }}{% endif %} frames
        </span>
        {% endif %}
        <div class="flex-grow"></div>

        <!-- Pause button for running renders -->
        {% if run.status == 'running' %}
        <button hx-post="/runs/{{ run.run_id }}/pause"
                hx-target="#action-result"
                hx-swap="innerHTML"
                class="bg-yellow-600 hover:bg-yellow-700 px-3 py-1 rounded text-sm font-medium">
            Pause
        </button>
        {% endif %}

        <!-- Resume/Restart buttons for failed/paused renders -->
        {% if run.status in ['failed', 'paused'] %}
            {% if run.checkpoint_frame %}
            <button hx-post="/runs/{{ run.run_id }}/resume"
                    hx-target="#action-result"
                    hx-swap="innerHTML"
                    class="bg-green-600 hover:bg-green-700 px-3 py-1 rounded text-sm font-medium">
                Resume{% if run.total_frames %} ({{ ((run.checkpoint_frame / run.total_frames) * 100)|round|int }}%){% endif %}
            </button>
            {% endif %}
            <button hx-post="/runs/{{ run.run_id }}/restart"
                    hx-target="#action-result"
                    hx-swap="innerHTML"
                    hx-confirm="Discard progress and start over?"
                    class="bg-yellow-600 hover:bg-yellow-700 px-3 py-1 rounded text-sm font-medium">
                Restart
            </button>
        {% endif %}

        {% if run.recipe %}
        <button hx-post="/runs/rerun/{{ run.recipe }}"
                hx-target="#action-result"
                hx-swap="innerHTML"
                class="bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded text-sm font-medium">
            Run Again
        </button>
        {% endif %}
        <button hx-post="/runs/{{ run.run_id }}/publish"
                hx-target="#action-result"
                class="bg-purple-600 hover:bg-purple-700 px-3 py-1 rounded text-sm font-medium">
            Share to L2
        </button>
        <button hx-delete="/runs/{{ run.run_id }}/ui"
                hx-target="#action-result"
                hx-confirm="Delete this run and all its artifacts? This cannot be undone."
                class="bg-red-600 hover:bg-red-700 px-3 py-1 rounded text-sm font-medium">
            Delete
        </button>
        <span id="action-result"></span>
    </div>

    <!-- Info Grid -->
    <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
        <div class="bg-gray-800 rounded-lg p-4">
            <div class="text-gray-500 text-sm">Recipe</div>
            <div class="text-white font-medium">
                {% if run.recipe %}
                <a href="/recipes/{{ run.recipe }}" class="hover:text-blue-400">
                    {{ run.recipe_name or (run.recipe[:16] ~ '...') }}
                </a>
                {% else %}
                Unknown
                {% endif %}
            </div>
        </div>
        <div class="bg-gray-800 rounded-lg p-4">
            <div class="text-gray-500 text-sm">Steps</div>
            <div class="text-white font-medium">
                {% if run.recipe == 'streaming' %}
                    {% if run.status == 'completed' %}1 / 1{% else %}0 / 1{% endif %}
                {% else %}
                    {{ run.executed or 0 }} / {{ run.total_steps or (plan.steps|length if plan and plan.steps else '?') }}
                {% endif %}
                {% if run.cached_steps %}
                <span class="text-purple-400 text-sm">({{ run.cached_steps }} cached)</span>
                {% endif %}
            </div>
        </div>
        <div class="bg-gray-800 rounded-lg p-4">
            <div class="text-gray-500 text-sm">Created</div>
            <div class="text-white font-medium">{{ run.created_at }}</div>
        </div>
        <div class="bg-gray-800 rounded-lg p-4">
            <div class="text-gray-500 text-sm">User</div>
            <div class="text-white font-medium">{{ run.username or 'Unknown' }}</div>
        </div>
    </div>

    <!-- Unified HLS Player (shown during rendering, for paused/failed runs with checkpoint, OR for completed HLS streams) -->
    {% if run.status == 'rendering' or run.ipfs_playlist_cid or (run.status in ['paused', 'failed'] and run.checkpoint_frame) %}
    <div id="hls-player-container" class="mb-6 bg-gray-800 rounded-lg p-4">
        <div class="flex items-center justify-between mb-4">
            <h3 class="text-lg font-semibold flex items-center">
                {% if run.status == 'rendering' %}
                <span id="live-indicator" class="w-3 h-3 bg-red-500 rounded-full mr-2 animate-pulse"></span>
                <span id="player-title">Live Preview</span>
                {% elif run.status == 'paused' %}
                <span id="live-indicator" class="w-3 h-3 bg-yellow-500 rounded-full mr-2"></span>
                <span id="player-title">Partial Output (Paused)</span>
                {% elif run.status == 'failed' and run.checkpoint_frame %}
                <span id="live-indicator" class="w-3 h-3 bg-red-500 rounded-full mr-2"></span>
                <span id="player-title">Partial Output (Failed)</span>
                {% else %}
                <span id="live-indicator" class="w-3 h-3 bg-green-500 rounded-full mr-2 hidden"></span>
                <span id="player-title">Video</span>
                {% endif %}
            </h3>
            <div class="flex items-center space-x-4">
                <!-- Mode toggle -->
                <div class="flex items-center space-x-2 text-sm">
                    <button id="mode-replay" onclick="setPlayerMode('replay')"
                            class="px-2 py-1 rounded {% if run.status != 'rendering' %}bg-blue-600 text-white{% else %}bg-gray-700 text-gray-400 hover:bg-gray-600{% endif %}">
                        From Start
                    </button>
                    <button id="mode-live" onclick="setPlayerMode('live')"
                            class="px-2 py-1 rounded {% if run.status == 'rendering' %}bg-blue-600 text-white{% else %}bg-gray-700 text-gray-400 hover:bg-gray-600{% endif %}">
                        Live Edge
                    </button>
                </div>
                <div id="stream-status" class="text-sm text-gray-400">Connecting...</div>
            </div>
        </div>
        <div class="relative bg-black rounded-lg overflow-hidden" style="aspect-ratio: 16/9;">
            <video id="hls-video" class="w-full h-full" controls autoplay muted playsinline></video>
            <div id="stream-loading" class="absolute inset-0 flex items-center justify-center bg-gray-900/80">
                <div class="text-center">
                    <div class="animate-spin w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full mx-auto mb-2"></div>
                    <div class="text-gray-400">Waiting for stream...</div>
                </div>
            </div>
        </div>
        <div class="mt-2 flex items-center justify-between text-xs text-gray-500">
            <span>Stream: <code class="bg-gray-900 px-1 rounded">/runs/{{ run.run_id }}/playlist.m3u8</code></span>
            <span id="stream-info"></span>
        </div>
    </div>

    <script>
    (function() {
        const video = document.getElementById('hls-video');
        const statusEl = document.getElementById('stream-status');
        const loadingEl = document.getElementById('stream-loading');
        const streamInfoEl = document.getElementById('stream-info');
        const liveIndicator = document.getElementById('live-indicator');
        const playerTitle = document.getElementById('player-title');
        const modeReplayBtn = document.getElementById('mode-replay');
        const modeLiveBtn = document.getElementById('mode-live');

        const baseUrl = '/runs/{{ run.run_id }}/playlist.m3u8';
        const isRendering = {{ 'true' if run.status == 'rendering' else 'false' }};
        const isPausedOrFailed = {{ 'true' if run.status in ['paused', 'failed'] else 'false' }};

        let hls = null;
        let retryCount = 0;
        const maxRetries = 120;
        let segmentsLoaded = 0;
        // Start in replay mode for paused/failed (shows partial output from start)
        // Start in live mode for rendering (follows the render progress)
        let currentMode = isRendering ? 'live' : 'replay';

        function getHlsUrl() {
            return baseUrl + '?_t=' + Date.now();
        }

        // Custom playlist loader that adds cache-busting to every request
        class CacheBustingPlaylistLoader extends Hls.DefaultConfig.loader {
            load(context, config, callbacks) {
                if (context.type === 'manifest' || context.type === 'level') {
                    const url = new URL(context.url, window.location.origin);
                    url.searchParams.set('_t', Date.now());
                    context.url = url.toString();
                }
                super.load(context, config, callbacks);
            }
        }

        function getHlsConfig(mode) {
            const baseConfig = {
                maxBufferLength: 120,
                maxMaxBufferLength: 180,
                maxBufferSize: 100 * 1024 * 1024,
                maxBufferHole: 0.5,
                backBufferLength: 60,
                manifestLoadingTimeOut: 10000,
                manifestLoadingMaxRetry: 4,
                levelLoadingTimeOut: 10000,
                levelLoadingMaxRetry: 4,
                fragLoadingTimeOut: 20000,
                fragLoadingMaxRetry: 6,
                startLevel: 0,
                abrEwmaDefaultEstimate: 500000,
            };

            if (mode === 'live') {
                // Live mode: follow the edge, cache-bust playlists
                return {
                    ...baseConfig,
                    pLoader: CacheBustingPlaylistLoader,
                    liveSyncDurationCount: 10,
                    liveMaxLatencyDurationCount: 20,
                    liveDurationInfinity: true,
                };
            } else {
                // Replay mode: start from beginning, no live sync
                return {
                    ...baseConfig,
                    pLoader: CacheBustingPlaylistLoader,  // Still bust cache for fresh playlist
                    startPosition: 0,
                    liveDurationInfinity: false,
                };
            }
        }

        function updateModeUI(mode) {
            currentMode = mode;
            if (mode === 'live') {
                modeLiveBtn.classList.add('bg-blue-600', 'text-white');
                modeLiveBtn.classList.remove('bg-gray-700', 'text-gray-400');
                modeReplayBtn.classList.remove('bg-blue-600', 'text-white');
                modeReplayBtn.classList.add('bg-gray-700', 'text-gray-400');
                liveIndicator.classList.remove('hidden', 'bg-green-500');
                liveIndicator.classList.add('bg-red-500', 'animate-pulse');
                playerTitle.textContent = isRendering ? 'Live Preview' : 'Live Edge';
            } else {
                modeReplayBtn.classList.add('bg-blue-600', 'text-white');
                modeReplayBtn.classList.remove('bg-gray-700', 'text-gray-400');
                modeLiveBtn.classList.remove('bg-blue-600', 'text-white');
                modeLiveBtn.classList.add('bg-gray-700', 'text-gray-400');
                liveIndicator.classList.add('hidden');
                liveIndicator.classList.remove('animate-pulse');
                playerTitle.textContent = 'Replay';
            }
        }

        window.setPlayerMode = function(mode) {
            if (mode === currentMode) return;

            const currentTime = video.currentTime;
            const wasPlaying = !video.paused;

            // Destroy current HLS instance
            if (hls) {
                hls.destroy();
                hls = null;
            }

            updateModeUI(mode);
            segmentsLoaded = 0;
            retryCount = 0;

            // Reinitialize with new config
            initHls(mode, mode === 'replay' ? 0 : null);  // Start from 0 in replay, live edge in live
        };

        function initHls(mode, startPosition) {
            mode = mode || currentMode;

            if (Hls.isSupported()) {
                const config = getHlsConfig(mode);
                if (startPosition !== null && startPosition !== undefined) {
                    config.startPosition = startPosition;
                }
                hls = new Hls(config);

                hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
                    loadingEl.classList.add('hidden');
                    statusEl.textContent = 'Buffering...';
                    statusEl.classList.remove('text-gray-400');
                    statusEl.classList.add('text-yellow-400');
                    streamInfoEl.textContent = `${data.levels.length} quality level(s)`;
                    video.play().catch(() => {});
                });

                hls.on(Hls.Events.FRAG_LOADED, function(event, data) {
                    retryCount = 0;
                    segmentsLoaded++;
                    const modeLabel = currentMode === 'live' ? 'Live' : 'Replay';
                    statusEl.textContent = `${modeLabel} (${segmentsLoaded} segments)`;
                    statusEl.classList.remove('text-yellow-400', 'text-gray-400');
                    statusEl.classList.add('text-green-400');
                });

                hls.on(Hls.Events.BUFFER_APPENDED, function() {
                    loadingEl.classList.add('hidden');
                });

                hls.on(Hls.Events.ERROR, function(event, data) {
                    console.log('HLS error:', data.type, data.details, data.fatal);

                    if (data.fatal) {
                        switch (data.type) {
                            case Hls.ErrorTypes.NETWORK_ERROR:
                                if (retryCount < maxRetries) {
                                    retryCount++;
                                    statusEl.textContent = `Waiting for stream... (${retryCount})`;
                                    statusEl.classList.remove('text-green-400');
                                    statusEl.classList.add('text-yellow-400');
                                    const delay = Math.min(1000 * Math.pow(1.5, Math.min(retryCount, 6)), 10000);
                                    setTimeout(() => {
                                        hls.loadSource(getHlsUrl());
                                    }, delay + Math.random() * 1000);
                                } else {
                                    statusEl.textContent = 'Stream unavailable';
                                    statusEl.classList.add('text-red-400');
                                }
                                break;
                            case Hls.ErrorTypes.MEDIA_ERROR:
                                console.log('Media error, attempting recovery');
                                hls.recoverMediaError();
                                break;
                            default:
                                statusEl.textContent = 'Stream error';
                                statusEl.classList.add('text-red-400');
                                break;
                        }
                    } else {
                        if (data.details === 'bufferStalledError') {
                            statusEl.textContent = 'Buffering...';
                            statusEl.classList.remove('text-green-400');
                            statusEl.classList.add('text-yellow-400');
                        }
                    }
                });

                video.addEventListener('waiting', function() {
                    if (currentMode === 'live' && hls && hls.liveSyncPosition) {
                        const liveEdge = hls.liveSyncPosition;
                        const behindLive = liveEdge - video.currentTime;
                        if (behindLive < 8) {
                            statusEl.textContent = 'Waiting for rendering...';
                        } else {
                            statusEl.textContent = 'Buffering...';
                        }
                    } else {
                        statusEl.textContent = 'Buffering...';
                    }
                    statusEl.classList.remove('text-green-400');
                    statusEl.classList.add('text-yellow-400');
                });

                video.addEventListener('playing', function() {
                    const modeLabel = currentMode === 'live' ? 'Live' : 'Replay';
                    statusEl.textContent = `${modeLabel} (${segmentsLoaded} segments)`;
                    statusEl.classList.remove('text-yellow-400');
                    statusEl.classList.add('text-green-400');
                });

                // Live mode: periodic check for catching up to live edge
                if (currentMode === 'live') {
                    setInterval(function() {
                        if (hls && !video.paused && hls.levels && hls.levels.length > 0) {
                            const buffered = video.buffered;
                            if (buffered.length > 0) {
                                const bufferEnd = buffered.end(buffered.length - 1);
                                const bufferAhead = bufferEnd - video.currentTime;
                                if (bufferAhead < 4) {
                                    statusEl.textContent = 'Waiting for rendering...';
                                    statusEl.classList.remove('text-green-400');
                                    statusEl.classList.add('text-yellow-400');
                                }
                            }
                        }
                    }, 1000);
                }

                hls.loadSource(getHlsUrl());
                hls.attachMedia(video);
            } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
                video.src = getHlsUrl();
                video.addEventListener('loadedmetadata', function() {
                    loadingEl.classList.add('hidden');
                    statusEl.textContent = 'Playing';
                    video.play().catch(() => {});
                });
            } else {
                statusEl.textContent = 'HLS not supported';
                statusEl.classList.add('text-red-400');
            }
        }

        // Initialize with appropriate mode
        updateModeUI(currentMode);
        initHls(currentMode);

        window.addEventListener('beforeunload', function() {
            if (hls) hls.destroy();
        });
    })();
    </script>
    {% endif %}

    <!-- Tabs -->
    <div class="border-b border-gray-700 mb-6">
        <nav class="flex space-x-8">
            <a href="#plan" class="tab-link border-b-2 border-blue-500 text-white pb-3 px-1"
               onclick="showTab('plan')">Plan</a>
            <a href="#artifacts" class="tab-link border-b-2 border-transparent text-gray-400 hover:text-white pb-3 px-1"
               onclick="showTab('artifacts')">Artifacts</a>
            {% if analysis %}
            <a href="#analysis" class="tab-link border-b-2 border-transparent text-gray-400 hover:text-white pb-3 px-1"
               onclick="showTab('analysis')">Analysis</a>
            {% endif %}
            <a href="#inputs" class="tab-link border-b-2 border-transparent text-gray-400 hover:text-white pb-3 px-1"
               onclick="showTab('inputs')">Inputs</a>
        </nav>
    </div>

    <!-- Plan Tab -->
    <div id="tab-plan" class="tab-content">
        {% if plan %}
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-4">
            <!-- DAG Visualization -->
            <div class="lg:col-span-2">
                <div id="dag-container" class="bg-gray-900 rounded-lg border border-gray-700 h-96"></div>
            </div>
            <!-- Node Detail Panel -->
            <div id="node-detail" class="bg-gray-800 rounded-lg border border-gray-700 p-4 h-96 overflow-y-auto">
                <div id="node-detail-empty" class="h-full flex items-center justify-center text-gray-500">
                    Click a node to view details
                </div>
                <div id="node-detail-content" class="hidden">
                    <div class="flex items-center justify-between mb-4">
                        <h3 id="node-name" class="text-lg font-semibold"></h3>
                        <span id="node-type" class="text-sm px-2 py-1 rounded"></span>
                    </div>
                    <div id="node-status" class="mb-4"></div>

                    <!-- Inputs -->
                    <div id="node-inputs-section" class="mb-4">
                        <h4 class="text-gray-400 text-sm mb-2">Inputs</h4>
                        <div id="node-inputs" class="space-y-2"></div>
                    </div>

                    <!-- Output -->
                    <div id="node-output-section">
                        <h4 class="text-gray-400 text-sm mb-2">Output</h4>
                        <div id="node-output"></div>
                    </div>

                    <!-- Config -->
                    <div id="node-config-section" class="mt-4 hidden">
                        <h4 class="text-gray-400 text-sm mb-2">Config</h4>
                        <pre id="node-config" class="text-xs bg-gray-900 rounded p-2 overflow-x-auto"></pre>
                    </div>
                </div>
            </div>
        </div>

        <!-- Step List -->
        <div class="space-y-2">
            {% for step in plan.steps %}
            {% set step_color = 'green' if step.status == 'completed' or step.cache_id else ('purple' if step.cached else ('blue' if step.status == 'running' else 'gray')) %}
            <div class="step-item bg-gray-800 rounded p-3 cursor-pointer hover:bg-gray-750 transition-colors"
                 data-step-id="{{ step.id }}"
                 data-step-name="{{ step.name }}"
                 data-step-type="{{ step.type }}"
                 data-step-status="{{ step.status or 'pending' }}"
                 data-step-inputs="{{ step.inputs | tojson }}"
                 data-step-cache-id="{{ step.cache_id or '' }}"
                 data-step-config="{{ (step.config or {}) | tojson }}"
                 onclick="selectStep('{{ step.id }}')">
                <div class="flex items-center justify-between">
                    <div class="flex items-center space-x-3">
                        <span class="w-6 h-6 rounded-full bg-{{ step_color }}-600 flex items-center justify-center text-xs">
                            {{ loop.index }}
                        </span>
                        <span class="font-medium">{{ step.name }}</span>
                        <span class="text-gray-500 text-sm">{{ step.type }}</span>
                    </div>
                    <div class="flex items-center space-x-3">
                        {% if step.cached %}
                        <span class="text-purple-400 text-sm">cached</span>
                        {% elif step.status == 'completed' %}
                        <span class="text-green-400 text-sm">completed</span>
                        {% endif %}
                    </div>
                </div>
                {% if step.cache_id %}
                <div class="mt-2 ml-9 flex items-center space-x-2">
                    <span class="text-gray-500 text-xs">Output:</span>
                    <a href="/cache/{{ step.cache_id }}" class="font-mono text-xs text-blue-400 hover:text-blue-300" onclick="event.stopPropagation()">
                        {{ step.cache_id[:24] }}...
                    </a>
                </div>
                {% endif %}
            </div>
            {% endfor %}
        </div>

        <!-- Recipe/Plan S-expression -->
        {% if plan_sexp %}
        <details class="mt-6" open>
            <summary class="cursor-pointer text-gray-400 hover:text-white text-sm mb-2 flex items-center justify-between">
                <span>Recipe (S-expression)</span>
                {% if recipe_ipfs_cid %}
                <a href="https://ipfs.io/ipfs/{{ recipe_ipfs_cid }}"
                   target="_blank"
                   onclick="event.stopPropagation()"
                   class="text-blue-400 hover:text-blue-300 text-xs font-mono ml-4">
                    ipfs://{{ recipe_ipfs_cid[:16] }}...
                </a>
                {% endif %}
            </summary>
            <div class="bg-gray-900 rounded-lg border border-gray-700 p-4 overflow-x-auto">
                <pre class="text-sm font-mono sexp-code">{{ plan_sexp }}</pre>
            </div>
        </details>
        {% endif %}

        <style>
        .sexp-code {
            line-height: 1.6;
        }
        </style>
        <script>
        // Single-pass S-expression syntax highlighter (avoids regex corruption)
        function highlightSexp(text) {
            const SPECIAL = new Set(['plan','recipe','def','->','stream','let','lambda','if','cond','define']);
            const PRIMS = new Set(['source','effect','sequence','segment','resize','transform','layer','blend','mux','analyze','fused-pipeline']);
            function esc(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
            function span(cls, s) { return '<span class="' + cls + '">' + esc(s) + '</span>'; }

            let out = '', i = 0, len = text.length;
            while (i < len) {
                // Comments
                if (text[i] === ';' && i + 1 < len && text[i+1] === ';') {
                    let end = text.indexOf('\n', i);
                    if (end === -1) end = len;
                    out += span('text-gray-500', text.slice(i, end));
                    i = end;
                }
                // Strings
                else if (text[i] === '"') {
                    let j = i + 1;
                    while (j < len && text[j] !== '"') { if (text[j] === '\\') j++; j++; }
                    if (j < len) j++; // closing quote
                    out += span('text-green-400', text.slice(i, j));
                    i = j;
                }
                // Keywords (:keyword)
                else if (text[i] === ':' && i + 1 < len && /[a-zA-Z_-]/.test(text[i+1])) {
                    let j = i + 1;
                    while (j < len && /[a-zA-Z0-9_-]/.test(text[j])) j++;
                    out += span('text-purple-400', text.slice(i, j));
                    i = j;
                }
                // Open paren - check for primitive/special after it
                else if (text[i] === '(') {
                    out += span('text-yellow-500', '(');
                    i++;
                    // Skip whitespace after paren
                    let ws = '';
                    while (i < len && (text[i] === ' ' || text[i] === '\t')) { ws += text[i]; i++; }
                    out += esc(ws);
                    // Check if next word is a special form or primitive
                    if (i < len && /[a-zA-Z_>-]/.test(text[i])) {
                        let j = i;
                        while (j < len && /[a-zA-Z0-9_>-]/.test(text[j])) j++;
                        let word = text.slice(i, j);
                        if (SPECIAL.has(word)) out += span('text-pink-400 font-semibold', word);
                        else if (PRIMS.has(word)) out += span('text-blue-400', word);
                        else out += esc(word);
                        i = j;
                    }
                }
                // Close paren
                else if (text[i] === ')') {
                    out += span('text-yellow-500', ')');
                    i++;
                }
                // Numbers
                else if (/[0-9]/.test(text[i]) && (i === 0 || /[\s(]/.test(text[i-1]))) {
                    let j = i;
                    while (j < len && /[0-9.]/.test(text[j])) j++;
                    out += span('text-orange-300', text.slice(i, j));
                    i = j;
                }
                // Regular text
                else {
                    let j = i;
                    while (j < len && !'(;":)'.includes(text[j])) {
                        if (text[j] === ':' && j + 1 < len && /[a-zA-Z_-]/.test(text[j+1])) break;
                        if (/[0-9]/.test(text[j]) && (j === 0 || /[\s(]/.test(text[j-1]))) break;
                        j++;
                    }
                    if (j === i) { out += esc(text[i]); i++; } // safety: advance at least 1 char
                    else { out += esc(text.slice(i, j)); i = j; }
                }
            }
            return out;
        }

        document.querySelectorAll('.sexp-code').forEach(el => {
            el.innerHTML = highlightSexp(el.textContent);
        });
        </script>
        {% else %}
        <p class="text-gray-500">No plan available for this run.</p>
        {% endif %}
    </div>

    <!-- Artifacts Tab -->
    <div id="tab-artifacts" class="tab-content hidden">
        {% if artifacts %}
        <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
            {% for artifact in artifacts %}
            <a href="/cache/{{ artifact.cid }}"
               class="bg-gray-800 rounded-lg p-4 hover:bg-gray-750 transition-colors">
                {% if artifact.media_type and artifact.media_type.startswith('image/') %}
                <img src="/cache/{{ artifact.cid }}/raw" alt=""
                     class="w-full h-32 object-cover rounded mb-2">
                {% elif artifact.media_type and artifact.media_type.startswith('video/') %}
                <video src="/cache/{{ artifact.cid }}/raw"
                       class="w-full h-32 object-cover rounded mb-2" muted></video>
                {% else %}
                <div class="w-full h-32 bg-gray-900 rounded mb-2 flex items-center justify-center text-gray-600">
                    {{ artifact.media_type or 'Unknown' }}
                </div>
                {% endif %}
                <div class="font-mono text-xs text-gray-500 truncate">{{ artifact.cid[:16] }}...</div>
                <div class="text-sm text-gray-400">{{ artifact.step_name }}</div>
            </a>
            {% endfor %}
        </div>
        {% else %}
        <p class="text-gray-500">No artifacts generated yet.</p>
        {% endif %}
    </div>

    <!-- Analysis Tab -->
    <div id="tab-analysis" class="tab-content hidden">
        {% if analysis %}
        <div class="space-y-6">
            {% for item in analysis %}
            <div class="bg-gray-800 rounded-lg p-6">
                <div class="flex items-center justify-between mb-4">
                    <h3 class="text-lg font-semibold">{{ item.input_name }}</h3>
                    <a href="/cache/{{ item.input_cid }}" class="font-mono text-xs text-blue-400 hover:text-blue-300">
                        {{ item.input_cid[:16] }}...
                    </a>
                </div>

                {% if item.has_analysis %}
                <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
                    {% if item.tempo %}
                    <div class="bg-gray-900 rounded p-3">
                        <div class="text-gray-500 text-sm">Tempo</div>
                        <div class="text-2xl font-bold text-pink-400">{{ item.tempo | round(1) }}</div>
                        <div class="text-gray-500 text-xs">BPM</div>
                    </div>
                    {% endif %}

                    {% if item.beat_times %}
                    <div class="bg-gray-900 rounded p-3">
                        <div class="text-gray-500 text-sm">Beats</div>
                        <div class="text-2xl font-bold text-blue-400">{{ item.beat_times | length }}</div>
                        <div class="text-gray-500 text-xs">detected</div>
                    </div>
                    {% endif %}

                    {% if item.energy is not none %}
                    <div class="bg-gray-900 rounded p-3">
                        <div class="text-gray-500 text-sm">Energy</div>
                        <div class="text-2xl font-bold text-green-400">{{ (item.energy * 100) | round(1) }}%</div>
                        <div class="text-gray-500 text-xs">average</div>
                    </div>
                    {% endif %}

                    {% if item.beat_times %}
                    <div class="bg-gray-900 rounded p-3">
                        <div class="text-gray-500 text-sm">Duration</div>
                        <div class="text-2xl font-bold text-purple-400">
                            {{ (item.beat_times[-1] | default(0)) | round(1) }}s
                        </div>
                        <div class="text-gray-500 text-xs">analyzed</div>
                    </div>
                    {% endif %}
                </div>

                <!-- Beat visualization -->
                {% if item.beat_times and item.beat_times | length > 0 %}
                <div class="mt-4">
                    <div class="text-gray-500 text-sm mb-2">Beat Timeline</div>
                    <div class="bg-gray-900 rounded h-8 relative overflow-hidden">
                        {% set max_time = item.beat_times[-1] if item.beat_times else 1 %}
                        {% for beat_time in item.beat_times[:100] %}
                        <div class="absolute top-0 bottom-0 w-px bg-pink-500 opacity-60"
                             style="left: {{ (beat_time / max_time * 100) | round(2) }}%"></div>
                        {% endfor %}
                    </div>
                    <div class="flex justify-between text-xs text-gray-600 mt-1">
                        <span>0s</span>
                        <span>{{ max_time | round(1) }}s</span>
                    </div>
                </div>
                {% endif %}

                <!-- Raw data toggle -->
                <details class="mt-4">
                    <summary class="cursor-pointer text-gray-400 hover:text-white text-sm">
                        Show Raw Analysis Data
                    </summary>
                    <div class="bg-gray-900 rounded-lg border border-gray-700 p-4 mt-2 overflow-x-auto">
                        <pre class="text-sm text-gray-300 whitespace-pre-wrap">{{ item.raw | tojson(indent=2) }}</pre>
                    </div>
                </details>
                {% else %}
                <p class="text-gray-500">No analysis data available for this input.</p>
                {% endif %}
            </div>
            {% endfor %}
        </div>
        {% else %}
        <p class="text-gray-500">No inputs with analysis data.</p>
        {% endif %}
    </div>

    <!-- Inputs Tab -->
    <div id="tab-inputs" class="tab-content hidden">
        {% if run_inputs %}
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
            {% for input in run_inputs %}
            <div class="bg-gray-800 rounded-lg overflow-hidden">
                <!-- Media Preview -->
                {% if input.media_type and input.media_type.startswith('image/') %}
                <a href="/cache/{{ input.cid }}" class="block">
                    <img src="/cache/{{ input.cid }}/raw" alt="{{ input.name or 'Input' }}"
                         class="w-full h-48 object-cover">
                </a>
                {% elif input.media_type and input.media_type.startswith('video/') %}
                <a href="/cache/{{ input.cid }}" class="block">
                    <video src="/cache/{{ input.cid }}/raw"
                           class="w-full h-48 object-cover" muted controls></video>
                </a>
                {% elif input.media_type and input.media_type.startswith('audio/') %}
                <div class="p-4 bg-gray-900">
                    <audio src="/cache/{{ input.cid }}/raw" controls class="w-full"></audio>
                </div>
                {% else %}
                <a href="/cache/{{ input.cid }}" class="block">
                    <div class="w-full h-48 bg-gray-900 flex items-center justify-center text-gray-600">
                        <div class="text-center">
                            <div class="text-4xl mb-2">📄</div>
                            <div>{{ input.media_type or 'Unknown type' }}</div>
                        </div>
                    </div>
                </a>
                {% endif %}
                <!-- Info -->
                <div class="p-3">
                    {% if input.name %}
                    <div class="font-medium text-white mb-1">{{ input.name }}</div>
                    {% endif %}
                    <a href="/cache/{{ input.cid }}" class="font-mono text-xs text-blue-400 hover:text-blue-300 block truncate">
                        {{ input.cid }}
                    </a>
                    {% if input.media_type %}
                    <div class="text-xs text-gray-500 mt-1">{{ input.media_type }}</div>
                    {% endif %}
                </div>
            </div>
            {% endfor %}
        </div>
        {% elif run.inputs %}
        <!-- Fallback to simple list if run_inputs not available -->
        <div class="space-y-2">
            {% for input_hash in run.inputs %}
            <a href="/cache/{{ input_hash }}"
               class="block bg-gray-800 rounded p-3 font-mono text-sm hover:bg-gray-750">
                {{ input_hash }}
            </a>
            {% endfor %}
        </div>
        {% else %}
        <p class="text-gray-500">No inputs recorded.</p>
        {% endif %}
    </div>

    <!-- Output -->
    {% if run.output_cid %}
    <div class="mt-8 bg-gray-800 rounded-lg p-6">
        <h3 class="text-lg font-semibold mb-4">Output</h3>

        {# Inline media preview - prefer IPFS URLs when available #}
        <div class="mb-4">
            {% if output_media_type and output_media_type.startswith('image/') %}
            <a href="{% if run.ipfs_cid %}/ipfs/{{ run.ipfs_cid }}{% else %}/cache/{{ run.output_cid }}{% endif %}" class="block">
                <img src="{% if run.ipfs_cid %}/ipfs/{{ run.ipfs_cid }}{% else %}/cache/{{ run.output_cid }}/raw{% endif %}" alt="Output"
                     class="max-w-full max-h-96 rounded-lg mx-auto">
            </a>
            {% elif output_media_type and output_media_type.startswith('video/') %}
            {# HLS streams use the unified player above; show direct video for non-HLS #}
            {% if run.ipfs_playlist_cid %}
            <div class="text-gray-400 text-sm py-4">
                HLS stream available in player above. Use "From Start" to watch from beginning or "Live Edge" to follow rendering progress.
            </div>
            {% else %}
            {# Direct video file #}
            <video src="{% if run.ipfs_cid %}/ipfs/{{ run.ipfs_cid }}{% else %}/cache/{{ run.output_cid }}/raw{% endif %}" controls
                   class="max-w-full max-h-96 rounded-lg mx-auto"></video>
            {% endif %}
            {% elif output_media_type and output_media_type.startswith('audio/') %}
            <audio src="{% if run.ipfs_cid %}/ipfs/{{ run.ipfs_cid }}{% else %}/cache/{{ run.output_cid }}/raw{% endif %}" controls class="w-full"></audio>
            {% else %}
            <div class="bg-gray-900 rounded-lg p-8 text-center text-gray-500">
                <div class="text-4xl mb-2">?</div>
                <div>{{ output_media_type or 'Unknown media type' }}</div>
            </div>
            {% endif %}
        </div>

        <div class="flex items-center justify-between">
            <a href="{% if run.ipfs_cid %}/ipfs/{{ run.ipfs_cid }}{% else %}/cache/{{ run.output_cid }}{% endif %}"
               class="font-mono text-sm text-blue-400 hover:text-blue-300">
                {% if run.ipfs_cid %}{{ run.ipfs_cid }}{% else %}{{ run.output_cid }}{% endif %}
            </a>
            <div class="flex items-center space-x-4">
                {% if run.ipfs_playlist_cid %}
                <a href="/ipfs/{{ run.ipfs_playlist_cid }}"
                   class="text-gray-400 hover:text-white text-sm">
                    HLS Playlist
                </a>
                {% endif %}
                {% if run.ipfs_cid %}
                <a href="https://ipfs.io/ipfs/{{ run.ipfs_cid }}"
                   target="_blank"
                   class="text-gray-400 hover:text-white text-sm">
                    View on IPFS Gateway
                </a>
                {% endif %}
            </div>
        </div>
    </div>
    {% endif %}
</div>

<script>
function showTab(name) {
    document.querySelectorAll('.tab-content').forEach(el => el.classList.add('hidden'));
    document.querySelectorAll('.tab-link').forEach(el => {
        el.classList.remove('border-blue-500', 'text-white');
        el.classList.add('border-transparent', 'text-gray-400');
    });
    document.getElementById('tab-' + name).classList.remove('hidden');
    event.target.classList.add('border-blue-500', 'text-white');
    event.target.classList.remove('border-transparent', 'text-gray-400');
}

{% if plan %}
// Store step data for quick lookup
const stepData = {};
document.querySelectorAll('.step-item').forEach(el => {
    const id = el.dataset.stepId;
    stepData[id] = {
        id: id,
        name: el.dataset.stepName,
        type: el.dataset.stepType,
        status: el.dataset.stepStatus,
        inputs: JSON.parse(el.dataset.stepInputs || '[]'),
        cacheId: el.dataset.stepCacheId,
        config: JSON.parse(el.dataset.stepConfig || '{}')
    };
});
console.log('stepData loaded:', Object.keys(stepData).length, 'steps');
console.log('dag_elements:', {{ dag_elements | tojson }});

let cy = null;
let selectedNode = null;

function selectStep(stepId) {
    // Update URL hash
    history.pushState(null, '', '#node-' + stepId);
    showNodeDetail(stepId);
}

function showNodeDetail(stepId) {
    const step = stepData[stepId];
    if (!step) return;

    selectedNode = stepId;

    // Update step list selection
    document.querySelectorAll('.step-item').forEach(el => {
        el.classList.remove('ring-2', 'ring-blue-500');
        if (el.dataset.stepId === stepId) {
            el.classList.add('ring-2', 'ring-blue-500');
        }
    });

    // Update cytoscape selection
    if (cy) {
        cy.nodes().removeClass('selected');
        cy.nodes().style('border-width', 0);
        const node = cy.$('#' + stepId);
        if (node.length) {
            node.style('border-width', 3);
            node.style('border-color', '#3b82f6');
        }
    }

    // Show detail panel
    document.getElementById('node-detail-empty').classList.add('hidden');
    document.getElementById('node-detail-content').classList.remove('hidden');

    // Populate node info
    document.getElementById('node-name').textContent = step.name;

    const typeEl = document.getElementById('node-type');
    typeEl.textContent = step.type;
    const typeColors = {
        'SOURCE': 'bg-blue-600',
        'EFFECT': 'bg-purple-600',
        'SEQUENCE': 'bg-pink-600',
        'transform': 'bg-green-600',
        'output': 'bg-yellow-600'
    };
    typeEl.className = 'text-sm px-2 py-1 rounded ' + (typeColors[step.type] || 'bg-gray-600');

    // Status
    const statusEl = document.getElementById('node-status');
    const statusColors = {
        'completed': 'text-green-400',
        'running': 'text-blue-400',
        'pending': 'text-gray-400',
        'cached': 'text-purple-400'
    };
    statusEl.innerHTML = `<span class="${statusColors[step.status] || 'text-gray-400'}">${step.status}</span>`;

    // Inputs
    const inputsEl = document.getElementById('node-inputs');
    if (step.inputs.length > 0) {
        document.getElementById('node-inputs-section').classList.remove('hidden');
        inputsEl.innerHTML = step.inputs.map(inp => {
            const inputStep = stepData[inp];
            const inputCacheId = inputStep ? inputStep.cacheId : '';
            if (inputCacheId) {
                return `<div class="bg-gray-900 rounded p-2">
                    <div class="text-sm text-gray-300 cursor-pointer hover:text-blue-400" onclick="selectStep('${inp}')">${inp}</div>
                    <a href="/cache/${inputCacheId}" class="font-mono text-xs text-blue-400 hover:text-blue-300 block mt-1" onclick="event.stopPropagation()">
                        ${inputCacheId.substring(0, 24)}...
                    </a>
                </div>`;
            } else {
                return `<div class="bg-gray-900 rounded p-2">
                    <div class="text-sm text-gray-300 cursor-pointer hover:text-blue-400" onclick="selectStep('${inp}')">${inp}</div>
                    <span class="text-xs text-gray-500">No cached output</span>
                </div>`;
            }
        }).join('');
    } else {
        document.getElementById('node-inputs-section').classList.add('hidden');
    }

    // Output
    const outputEl = document.getElementById('node-output');
    if (step.cacheId) {
        outputEl.innerHTML = `
            <div class="bg-gray-900 rounded p-2">
                <a href="/cache/${step.cacheId}" class="font-mono text-xs text-blue-400 hover:text-blue-300 block">
                    ${step.cacheId}
                </a>
                <a href="/cache/${step.cacheId}/raw" target="_blank" class="text-xs text-gray-500 hover:text-gray-300 mt-1 inline-block">
                    View raw
                </a>
            </div>`;
    } else {
        outputEl.innerHTML = '<span class="text-gray-500 text-sm">No output yet</span>';
    }

    // Config
    const configSection = document.getElementById('node-config-section');
    const configEl = document.getElementById('node-config');
    if (step.config && Object.keys(step.config).length > 0) {
        configSection.classList.remove('hidden');
        configEl.textContent = JSON.stringify(step.config, null, 2);
    } else {
        configSection.classList.add('hidden');
    }
}

// Initialize DAG
document.addEventListener('DOMContentLoaded', function() {
    cy = cytoscape({
        container: document.getElementById('dag-container'),
        style: [
            { selector: 'node', style: {
                'label': 'data(label)',
                'background-color': 'data(color)',
                'color': '#fff',
                'text-valign': 'center',
                'font-size': '10px',
                'width': 40,
                'height': 40,
                'border-width': 0
            }},
            { selector: 'edge', style: {
                'width': 2,
                'line-color': '#4b5563',
                'target-arrow-color': '#4b5563',
                'target-arrow-shape': 'triangle',
                'curve-style': 'bezier'
            }}
        ],
        elements: {{ dag_elements | tojson }},
        layout: { name: 'dagre', rankDir: 'LR', padding: 30 }
    });

    // Node click handler
    cy.on('tap', 'node', function(evt) {
        const nodeId = evt.target.id();
        selectStep(nodeId);
    });

    // Handle initial hash
    const hash = window.location.hash;
    if (hash && hash.startsWith('#node-')) {
        const nodeId = hash.substring(6);
        if (stepData[nodeId]) {
            showNodeDetail(nodeId);
        }
    }
});

// Handle hash changes
window.addEventListener('hashchange', function() {
    const hash = window.location.hash;
    if (hash && hash.startsWith('#node-')) {
        const nodeId = hash.substring(6);
        if (stepData[nodeId]) {
            showNodeDetail(nodeId);
        }
    }
});
{% endif %}
</script>
{% endblock %}
