<!DOCTYPE html>
<html class="light" lang="en">
<head>
    <meta charset="utf-8"/>
    <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
    <title>Judge LLM - Evaluation Report</title>
    <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet"/>
    <script>
        tailwind.config = {
            darkMode: "class",
            theme: {
                extend: {
                    colors: {
                        "primary": "#0d7ff2",
                        "background-light": "#f5f7f8",
                        "background-dark": "#101922",
                        "sidebar-light": "#e9eef2",
                        "sidebar-dark": "#182430",
                    },
                    fontFamily: {
                        "display": ["Inter", "sans-serif"]
                    },
                },
            },
        }
    </script>
    <style>
        .prose-compact p { margin: 0; }
        .prose-compact p + p { margin-top: 0.375rem; }
        .prose-compact li { margin-top: 0.125rem; }
        .prose-compact pre { margin: 0.375rem 0; }
        .prose-compact code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
    </style>
</head>
<body class="bg-background-light dark:bg-background-dark font-display text-gray-800 dark:text-gray-200">
<div class="flex h-screen flex-col">
    <!-- Page Header -->
    <header class="flex h-16 items-center justify-between border-b border-gray-200 dark:border-gray-700 bg-background-light dark:bg-background-dark px-6 shadow-sm">
        <div class="flex items-center gap-4">
            <h1 class="text-xl font-bold">Judge LLM Evaluation Report</h1>
            <a href="https://github.com/HiHelloAI/judge-llm" target="_blank" rel="noopener noreferrer" class="text-xs text-gray-500 dark:text-gray-400 hover:text-primary flex items-center gap-1">
                <span>&#11088;</span>
                <span>Star on GitHub</span>
            </a>
        </div>
        <div class="flex items-center gap-4 text-sm">
            <span id="overall-status" class="font-bold"></span>
            <div class="flex items-center gap-3 text-xs">
                <span class="text-gray-600 dark:text-gray-400">Executions: <span id="total-executions" class="font-semibold text-gray-800 dark:text-gray-200"></span></span>
                <span class="text-gray-400">&bull;</span>
                <span class="text-gray-600 dark:text-gray-400">Success Rate: <span id="success-rate" class="font-semibold text-gray-800 dark:text-gray-200"></span></span>
                <span class="text-gray-400">&bull;</span>
                <span class="text-gray-600 dark:text-gray-400">Cost: <span id="total-cost" class="font-semibold text-gray-800 dark:text-gray-200"></span></span>
                <span class="text-gray-400">&bull;</span>
                <span class="text-gray-600 dark:text-gray-400">Time: <span id="total-time" class="font-semibold text-gray-800 dark:text-gray-200"></span></span>
            </div>
            <span id="generated-at" class="text-xs text-gray-500 dark:text-gray-400"></span>
        </div>
    </header>

    <div class="flex flex-1 overflow-hidden">
        <!-- Left Sidebar - Execution List -->
        <aside class="w-80 flex-shrink-0 bg-sidebar-light dark:bg-sidebar-dark p-4 border-r border-gray-200 dark:border-gray-700 flex flex-col overflow-hidden">
            <!-- Search Bar -->
            <div class="mb-4">
                <div class="relative">
                    <input
                        type="text"
                        id="search-input"
                        placeholder="Search executions..."
                        class="w-full px-4 py-2 pl-10 text-sm bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
                        oninput="filterExecutions()"
                    />
                    <span class="material-symbols-outlined absolute left-3 top-2 text-gray-400 text-lg">search</span>
                </div>
            </div>

            <!-- Execution List -->
            <div class="flex-1 overflow-y-auto">
                <h2 class="px-3 text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-2">Executions</h2>
                <div id="execution-list" class="space-y-1"></div>
            </div>
        </aside>

        <!-- Main Panel - Execution Details -->
        <main class="flex flex-1 flex-col bg-background-light dark:bg-background-dark overflow-hidden">
            <!-- Summary Bar -->
            <div id="summary-bar" class="hidden bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-3">
                <div class="flex items-center justify-between text-sm">
                    <span id="summary-text" class="text-gray-700 dark:text-gray-300"></span>
                </div>
            </div>

            <div id="main-content" class="flex-1 overflow-y-auto p-6">
                <div class="text-center text-gray-500 dark:text-gray-400 py-20">
                    <p class="text-lg">Select an execution from the left to view details</p>
                </div>
            </div>
        </main>
    </div>
</div>

<script>
    // Embedded report data
    const reportData = {{ report_data }};

    // Initialize the report
    function initializeReport() {
        const overallStatus = document.getElementById('overall-status');
        if (reportData.overall_success) {
            overallStatus.textContent = '\u2713 PASSED';
            overallStatus.className = 'font-bold text-green-600';
        } else {
            overallStatus.textContent = '\u2717 FAILED';
            overallStatus.className = 'font-bold text-red-600';
        }

        document.getElementById('generated-at').textContent =
            `Generated: ${new Date(reportData.generated_at).toLocaleString()}`;
        document.getElementById('total-executions').textContent = reportData.execution_runs.length;
        document.getElementById('success-rate').textContent =
            `${(reportData.success_rate * 100).toFixed(1)}%`;
        document.getElementById('total-cost').textContent =
            `$${reportData.total_cost.toFixed(4)}`;
        document.getElementById('total-time').textContent =
            `${reportData.total_time.toFixed(2)}s`;

        renderExecutionList();
    }

    function renderExecutionList(filteredData = null) {
        const list = document.getElementById('execution-list');
        list.innerHTML = '';
        const dataToRender = filteredData || reportData.execution_runs;

        // Group executions by directory structure (from source_path)
        const groups = {};
        dataToRender.forEach((exec, index) => {
            const sourcePath = exec.source_path || '';
            // Extract directory part (e.g., "subdir/nested/file.json" -> "subdir/nested")
            const lastSlash = sourcePath.lastIndexOf('/');
            const dirKey = lastSlash > 0 ? sourcePath.substring(0, lastSlash) : (sourcePath ? '.' : '');
            if (!groups[dirKey]) groups[dirKey] = [];
            groups[dirKey].push({ exec, index });
        });

        const sortedDirs = Object.keys(groups).sort();
        const hasMultipleDirs = sortedDirs.length > 1 || (sortedDirs.length === 1 && sortedDirs[0] !== '');

        sortedDirs.forEach(dirKey => {
            // Render directory header if there are directory groupings
            if (hasMultipleDirs) {
                const dirHeader = document.createElement('div');
                const displayDir = dirKey === '' ? 'Ungrouped' : dirKey === '.' ? 'Root' : dirKey;
                const dirCount = groups[dirKey].length;
                const dirPassed = groups[dirKey].filter(g => g.exec.overall_success).length;
                const dirColor = dirPassed === dirCount ? 'text-green-600' : 'text-yellow-600';
                dirHeader.className = 'px-3 py-1.5 mt-2 first:mt-0';
                dirHeader.innerHTML = `
                    <div class="flex items-center gap-1.5">
                        <span class="material-symbols-outlined text-gray-400 text-sm">folder</span>
                        <span class="text-xs font-semibold text-gray-700 dark:text-gray-300 truncate" title="${esc(displayDir)}">${esc(displayDir)}</span>
                        <span class="${dirColor} text-xs ml-auto">${dirPassed}/${dirCount}</span>
                    </div>
                `;
                list.appendChild(dirHeader);
            }

            groups[dirKey].forEach(({ exec, index }) => {
                const item = document.createElement('div');
                item.className = 'flex cursor-pointer items-center gap-2 rounded-lg p-2 hover:bg-gray-200 dark:hover:bg-gray-700/50 transition-colors' + (hasMultipleDirs ? ' ml-3' : '');
                item.setAttribute('data-execution-index', index);
                item.onclick = () => showExecutionDetails(index);

                const statusIcon = exec.overall_success ? '\u2713' : '\u2717';
                const statusColor = exec.overall_success ? 'text-green-500' : 'text-red-500';
                const passedCount = exec.evaluator_results.filter(e => e.passed).length;
                const totalCount = exec.evaluator_results.length;

                item.innerHTML = `
                    <div class="flex-shrink-0 ${statusColor} font-bold text-lg">${statusIcon}</div>
                    <div class="flex-1 overflow-hidden min-w-0">
                        <div class="flex items-center justify-between gap-1 mb-0.5">
                            <h3 class="truncate text-xs font-semibold text-gray-900 dark:text-gray-100">${exec.eval_case_id}</h3>
                            <span class="flex-shrink-0 text-xs text-gray-500 dark:text-gray-400">#${exec.run_number}</span>
                        </div>
                        <p class="truncate text-xs text-gray-600 dark:text-gray-400">
                            ${passedCount}/${totalCount} passed &bull; ${exec.provider_result.time_taken.toFixed(1)}s &bull; $${exec.provider_result.cost.toFixed(4)}
                        </p>
                    </div>
                `;

                list.appendChild(item);
            });
        });

        if (dataToRender.length === 0) {
            list.innerHTML = '<div class="text-center text-sm text-gray-500 dark:text-gray-400 py-4">No executions found</div>';
        }
    }

    function filterExecutions() {
        const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
        if (!searchTerm) { renderExecutionList(); return; }

        const filtered = reportData.execution_runs.filter(exec => {
            return `${exec.eval_case_id} ${exec.execution_id} ${exec.provider_type} ${exec.run_number} ${exec.source_path || ''} ${exec.evaluator_results.map(e => e.evaluator_name).join(' ')}`.toLowerCase().includes(searchTerm);
        });
        renderExecutionList(filtered);
    }

    // ── Helpers ──────────────────────────────────────────────────────────

    function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }

    /** Lightweight markdown-to-HTML for chat bubbles. Handles common patterns. */
    function md(raw) {
        if (!raw) return '';
        let s = esc(raw);
        // Code blocks (``` ... ```)
        s = s.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) =>
            `<pre class="bg-gray-100 dark:bg-gray-900 rounded p-2 my-1 text-xs overflow-auto"><code>${code.trim()}</code></pre>`);
        // Inline code
        s = s.replace(/`([^`]+)`/g, '<code class="bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded text-xs">$1</code>');
        // Bold
        s = s.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
        // Italic
        s = s.replace(/\*(.+?)\*/g, '<em>$1</em>');
        // Headings (### / ## / #) — only at line start
        s = s.replace(/^### (.+)$/gm, '<div class="font-semibold text-sm mt-2 mb-1">$1</div>');
        s = s.replace(/^## (.+)$/gm, '<div class="font-semibold text-sm mt-2 mb-1">$1</div>');
        s = s.replace(/^# (.+)$/gm, '<div class="font-bold text-sm mt-2 mb-1">$1</div>');
        // Unordered lists (- item or * item)
        s = s.replace(/^[\-\*] (.+)$/gm, '<li class="ml-4 list-disc">$1</li>');
        // Ordered lists (1. item)
        s = s.replace(/^\d+\. (.+)$/gm, '<li class="ml-4 list-decimal">$1</li>');
        // Paragraphs: convert double newlines to breaks
        s = s.replace(/\n\n/g, '</p><p class="mt-1.5">');
        // Single newlines to <br>
        s = s.replace(/\n/g, '<br>');
        return `<p>${s}</p>`;
    }

    function toggle(id) {
        const el = document.getElementById(id);
        if (el) el.classList.toggle('hidden');
    }

    function toggleEvalDetails(idx) {
        const row = document.getElementById(`eval-detail-${idx}`);
        const btn = document.getElementById(`eval-btn-${idx}`);
        const isHidden = row.classList.contains('hidden');

        // Collapse all other open eval details first
        document.querySelectorAll('[id^="eval-detail-"]').forEach(el => {
            if (el !== row && !el.classList.contains('hidden')) {
                el.classList.add('hidden');
                const otherIdx = el.id.replace('eval-detail-', '');
                const otherBtn = document.getElementById(`eval-btn-${otherIdx}`);
                if (otherBtn) otherBtn.textContent = 'View Details';
            }
        });

        // Toggle the clicked one
        row.classList.toggle('hidden', !isHidden);
        btn.textContent = isHidden ? 'Hide' : 'View Details';
    }

    function toggleConvFull(idx) {
        const el = document.getElementById(`conv-full-${idx}`);
        const btn = document.getElementById(`conv-full-btn-${idx}`);
        const hidden = el.classList.toggle('hidden');
        btn.innerHTML = hidden
            ? '<span class="material-symbols-outlined text-xs">unfold_more</span> View Full Response'
            : '<span class="material-symbols-outlined text-xs">unfold_less</span> Hide Full Response';
    }

    function toggleSection(id) {
        const el = document.getElementById(id);
        const btn = document.getElementById(`${id}-btn`);
        const hidden = el.classList.toggle('hidden');
        if (btn) {
            const arrow = btn.querySelector('.arrow');
            if (arrow) arrow.textContent = hidden ? '\u25B6' : '\u25BC';
        }
    }

    // ── Score bar helper ─────────────────────────────────────────────────

    function scoreBar(score, threshold) {
        if (score === null && threshold === null) return '';
        const pct = score !== null ? Math.min(Math.max(score * 100, 0), 100) : 0;
        const threshPct = threshold !== null ? Math.min(Math.max(threshold * 100, 0), 100) : null;
        const color = score !== null && threshold !== null
            ? (score >= threshold ? 'bg-green-500' : 'bg-red-500')
            : 'bg-blue-500';

        let html = `<div class="flex items-center gap-2 min-w-[140px]">`;
        html += `<div class="relative flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-visible">`;
        html += `<div class="${color} h-2 rounded-full" style="width:${pct}%"></div>`;
        if (threshPct !== null) {
            html += `<div class="absolute top-0 h-2 w-0.5 bg-gray-800 dark:bg-gray-300" style="left:${threshPct}%" title="Threshold: ${threshold}"></div>`;
        }
        html += `</div>`;
        if (score !== null) html += `<span class="text-xs font-semibold tabular-nums w-10 text-right">${score.toFixed(2)}</span>`;
        html += `</div>`;
        return html;
    }

    // ── Evaluator-specific detail rendering ──────────────────────────────

    function renderEvalDetail(evalResult, exec) {
        const details = evalResult.details || {};
        const keys = Object.keys(details);
        const etype = (evalResult.evaluator_type || evalResult.evaluator_name || '').toLowerCase();

        // Determine if this evaluator needs conversation context
        const needsConversation = etype.includes('response') || etype.includes('llm_judge')
            || etype.includes('embedding') || etype.includes('similarity')
            || etype.includes('trajectory') || etype.includes('subagent') || etype.includes('sub_agent');
        const showResponse = etype.includes('response') || etype.includes('llm_judge') || etype.includes('embedding') || etype.includes('similarity');
        const showTools = etype.includes('trajectory');
        const showAgents = etype.includes('subagent') || etype.includes('sub_agent');

        let html = '';

        // Error banner
        if (evalResult.error) {
            html += `<div class="mb-3 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
                <h5 class="font-semibold text-red-600 dark:text-red-400 text-sm mb-1">Error</h5>
                <p class="text-red-600 dark:text-red-300 text-xs">${esc(evalResult.error)}</p>
            </div>`;
        }

        // ── Paired expected/actual from details dict ──
        const pairs = [];
        const metadata = {};
        const pairedKeys = new Set();
        const expectedPrefixes = ['expected_', 'reference_'];
        const actualPrefixes = ['actual_', 'extracted_', 'observed_'];

        keys.forEach(key => {
            expectedPrefixes.forEach(ep => {
                if (key.startsWith(ep)) {
                    const base = key.slice(ep.length);
                    const ak = actualPrefixes.map(ap => ap + base).find(k => k in details) || base;
                    if (ak in details) {
                        pairs.push({ label: base.replace(/_/g, ' '), expected: details[key], actual: details[ak] });
                        pairedKeys.add(key); pairedKeys.add(ak);
                    }
                }
            });
        });
        if ('expected' in details && 'actual' in details && !pairedKeys.has('expected')) {
            pairs.push({ label: 'response', expected: details.expected, actual: details.actual });
            pairedKeys.add('expected'); pairedKeys.add('actual');
        }
        if ('reference' in details && 'result' in details && !pairedKeys.has('reference')) {
            pairs.push({ label: 'response', expected: details.reference, actual: details.result });
            pairedKeys.add('reference'); pairedKeys.add('result');
        }
        keys.forEach(k => { if (!pairedKeys.has(k)) metadata[k] = details[k]; });

        // Render paired comparisons
        if (pairs.length > 0) {
            html += '<div class="mb-4">';
            html += '<h5 class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-2">Actual vs Expected</h5>';
            pairs.forEach(pair => {
                const expStr = typeof pair.expected === 'object' ? JSON.stringify(pair.expected, null, 2) : String(pair.expected ?? '');
                const actStr = typeof pair.actual === 'object' ? JSON.stringify(pair.actual, null, 2) : String(pair.actual ?? '');
                const isMatch = expStr === actStr;
                html += `
                    <div class="mb-3">
                        <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1 capitalize">${esc(pair.label)}</div>
                        <div class="grid grid-cols-2 gap-3">
                            <div>
                                <div class="flex items-center gap-1 mb-1"><span class="material-symbols-outlined text-purple-600 text-sm">smart_toy</span><span class="text-xs font-medium text-gray-500">Actual</span>${isMatch ? '<span class="text-xs text-green-600 ml-1">Match</span>' : '<span class="text-xs text-yellow-600 ml-1">Differs</span>'}</div>
                                <pre class="bg-white dark:bg-gray-800 p-2 rounded border ${isMatch ? 'border-green-300 dark:border-green-700' : 'border-yellow-300 dark:border-yellow-700'} text-xs whitespace-pre-wrap overflow-auto max-h-48">${esc(actStr)}</pre>
                            </div>
                            <div>
                                <div class="flex items-center gap-1 mb-1"><span class="material-symbols-outlined text-green-600 text-sm">check_circle</span><span class="text-xs font-medium text-gray-500">Expected</span></div>
                                <pre class="bg-white dark:bg-gray-800 p-2 rounded border border-gray-200 dark:border-gray-700 text-xs whitespace-pre-wrap overflow-auto max-h-48">${esc(expStr)}</pre>
                            </div>
                        </div>
                    </div>`;
            });
            html += '</div>';
        }

        // Separate metadata into simple scalars vs complex/long values
        const metaKeys = Object.keys(metadata);
        const scalarItems = [];
        const complexItems = [];
        metaKeys.forEach(k => {
            const val = metadata[k];
            const isSimple = val === null || val === undefined || typeof val === 'boolean'
                || typeof val === 'number'
                || (typeof val === 'string' && val.length <= 120 && !val.includes('\n'));
            if (isSimple) {
                scalarItems.push({ key: k, val });
            } else {
                complexItems.push({ key: k, val });
            }
        });

        if (scalarItems.length > 0 || complexItems.length > 0) {
            html += `<div class="${pairs.length > 0 ? 'mt-3 pt-3 border-t border-gray-200 dark:border-gray-700' : 'mb-4'}">`;

            // Scalar data points - compact inline strip
            if (scalarItems.length > 0) {
                html += '<div class="flex flex-wrap gap-x-4 gap-y-1 mb-3">';
                scalarItems.forEach(item => {
                    const isNum = typeof item.val === 'number';
                    const isBool = typeof item.val === 'boolean';
                    let displayVal;
                    if (isNum) displayVal = Number.isInteger(item.val) ? String(item.val) : item.val.toFixed(4);
                    else if (isBool) displayVal = item.val ? 'true' : 'false';
                    else displayVal = String(item.val ?? 'N/A');

                    html += `<span class="text-xs text-gray-500 dark:text-gray-400">${esc(item.key.replace(/_/g, ' '))}: <span class="font-semibold text-gray-900 dark:text-gray-100">${esc(displayVal)}</span></span>`;
                });
                html += '</div>';
            }

            // Complex values - full-width expandable blocks
            if (complexItems.length > 0) {
                html += '<h5 class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-2">Details</h5>';
                html += '<div class="space-y-2">';
                complexItems.forEach((item, ci) => {
                    const isObj = typeof item.val === 'object' && item.val !== null;
                    const display = isObj ? JSON.stringify(item.val, null, 2) : String(item.val ?? '');
                    const detailId = `meta-complex-${evalResult.evaluator_name.replace(/\W/g, '_')}-${ci}`;
                    const previewLen = 200;
                    const needsTruncate = display.length > previewLen;

                    html += `<div class="bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
                        <div class="flex items-center justify-between px-3 py-1.5 border-b border-gray-100 dark:border-gray-700">
                            <span class="text-xs font-medium text-gray-600 dark:text-gray-400">${esc(item.key.replace(/_/g, ' '))}</span>
                            ${needsTruncate ? `<button onclick="toggle('${detailId}')" class="text-xs text-primary hover:text-blue-700">expand</button>` : ''}
                        </div>
                        <pre class="p-3 text-xs whitespace-pre-wrap overflow-auto ${needsTruncate ? 'max-h-24' : 'max-h-96'}" ${needsTruncate ? `id="${detailId}-preview"` : ''}>${esc(needsTruncate ? display.slice(0, previewLen) + '...' : display)}</pre>
                        ${needsTruncate ? `<pre id="${detailId}" class="hidden p-3 text-xs whitespace-pre-wrap overflow-auto max-h-[32rem]">${esc(display)}</pre>` : ''}
                    </div>`;
                });
                html += '</div>';
            }

            html += '</div>';
        }

        // Conversation comparison (only if evaluator needs it)
        if (needsConversation) {
            const actualConv = exec.provider_result.conversation_history || [];
            const expectedConv = (exec.eval_case && exec.eval_case.conversation) || [];
            const maxInv = Math.max(actualConv.length, expectedConv.length);
            const uid = evalResult.evaluator_name.replace(/\W/g, '_');

            if (maxInv > 0) {
                html += `<div class="${pairs.length > 0 || metaKeys.length > 0 ? 'mt-3 pt-3 border-t border-gray-200 dark:border-gray-700' : ''}">`;
                html += '<h5 class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400 mb-2">Conversation Comparison</h5>';
                html += '<div class="space-y-2">';

                for (let i = 0; i < maxInv; i++) {
                    const inv = actualConv[i];
                    const expInv = expectedConv[i];
                    const userText = inv ? inv.user_content.parts.map(p => p.text || '').join(' ') : (expInv ? expInv.user_content.parts.map(p => p.text || '').join(' ') : '');
                    const actualResp = inv ? inv.final_response.parts.map(p => p.text || '').join(' ') : '';
                    const expectedResp = expInv ? expInv.final_response.parts.map(p => p.text || '').join(' ') : '';

                    const aTools = inv && inv.intermediate_data ? inv.intermediate_data.tool_uses || [] : [];
                    const eTools = expInv && expInv.intermediate_data ? expInv.intermediate_data.tool_uses || [] : [];
                    const aAgents = inv && inv.intermediate_data ? inv.intermediate_data.intermediate_responses || [] : [];
                    const eAgents = expInv && expInv.intermediate_data ? expInv.intermediate_data.intermediate_responses || [] : [];
                    const eToolNames = eTools.map(t => t.name).sort();
                    const aToolNames = aTools.map(t => t.name).sort();

                    html += `<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">`;
                    // Turn header with user message
                    html += `<div class="bg-blue-50 dark:bg-blue-900/20 px-3 py-1.5 flex items-center gap-2 border-b border-gray-200 dark:border-gray-700">
                        <span class="text-xs font-bold text-gray-500 bg-gray-200 dark:bg-gray-700 px-1.5 py-0.5 rounded">Turn ${i + 1}</span>
                        <span class="material-symbols-outlined text-blue-600 text-xs">person</span>
                        <span class="text-xs text-gray-700 dark:text-gray-300 truncate">${esc(userText) || '<em class="text-gray-400">Empty</em>'}</span>
                    </div>`;

                    // Side-by-side actual vs expected
                    html += `<div class="grid grid-cols-2 divide-x divide-gray-200 dark:divide-gray-700">`;

                    // Actual column
                    html += `<div class="p-3 space-y-2">
                        <div class="flex items-center gap-1"><span class="material-symbols-outlined text-purple-600 text-xs">smart_toy</span><span class="text-xs font-semibold text-gray-500">Actual</span></div>`;
                    if (showResponse) {
                        html += `<div class="bg-white dark:bg-gray-800 p-2.5 rounded-lg border border-gray-200 dark:border-gray-700 text-xs leading-relaxed overflow-auto prose-compact">${actualResp ? md(actualResp) : '<em class="text-gray-400">No response</em>'}</div>`;
                    }
                    if (showTools && aTools.length > 0) {
                        html += `<div class="space-y-1">`;
                        aTools.forEach((t, ti) => {
                            const found = eToolNames.includes(t.name);
                            html += `<div class="flex items-center gap-1 text-xs">
                                <span class="px-1.5 py-0.5 rounded bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 font-medium">${esc(t.name)}</span>
                                ${!found && eTools.length > 0 ? '<span class="text-yellow-600 italic">unexpected</span>' : ''}
                                <button onclick="toggle('${uid}-at-${i}-${ti}')" class="text-primary hover:text-blue-700 ml-auto">args</button>
                            </div>
                            <pre id="${uid}-at-${i}-${ti}" class="hidden text-xs bg-white dark:bg-gray-800 p-1.5 rounded border border-gray-200 dark:border-gray-700 overflow-auto max-h-20 mt-0.5">${esc(JSON.stringify(t.args || {}, null, 2))}</pre>`;
                        });
                        html += `</div>`;
                    }
                    if (showAgents && aAgents.length > 0) {
                        html += `<div class="space-y-1">`;
                        aAgents.forEach((r, ri) => {
                            const name = r.agent_name || 'unknown';
                            const txt = (r.parts || []).map(p => p.text || '').join(' ');
                            html += `<div class="flex items-center gap-1 text-xs">
                                <span class="px-1.5 py-0.5 rounded bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 font-medium">${esc(name)}</span>
                                ${txt ? `<button onclick="toggle('${uid}-aa-${i}-${ri}')" class="text-primary hover:text-blue-700 ml-auto">response</button>` : ''}
                            </div>
                            ${txt ? `<pre id="${uid}-aa-${i}-${ri}" class="hidden text-xs bg-white dark:bg-gray-800 p-1.5 rounded border border-gray-200 dark:border-gray-700 overflow-auto max-h-20 mt-0.5 whitespace-pre-wrap">${esc(txt)}</pre>` : ''}`;
                        });
                        html += `</div>`;
                    }
                    html += `</div>`;

                    // Expected column
                    html += `<div class="p-3 space-y-2">
                        <div class="flex items-center gap-1"><span class="material-symbols-outlined text-green-600 text-xs">check_circle</span><span class="text-xs font-semibold text-gray-500">Expected</span></div>`;
                    if (showResponse) {
                        html += `<div class="bg-white dark:bg-gray-800 p-2.5 rounded-lg border border-gray-200 dark:border-gray-700 text-xs leading-relaxed overflow-auto prose-compact">${expectedResp && expectedResp.trim() ? md(expectedResp.trim()) : '<em class="text-gray-400">Not defined</em>'}</div>`;
                    }
                    if (showTools && eTools.length > 0) {
                        html += `<div class="space-y-1">`;
                        eTools.forEach((t, ti) => {
                            const found = aToolNames.includes(t.name);
                            html += `<div class="flex items-center gap-1 text-xs">
                                <span class="px-1.5 py-0.5 rounded bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 font-medium">${esc(t.name)}</span>
                                ${found ? '<span class="text-green-600">found</span>' : '<span class="text-red-600 italic">missing</span>'}
                                <button onclick="toggle('${uid}-et-${i}-${ti}')" class="text-primary hover:text-blue-700 ml-auto">args</button>
                            </div>
                            <pre id="${uid}-et-${i}-${ti}" class="hidden text-xs bg-white dark:bg-gray-800 p-1.5 rounded border border-gray-200 dark:border-gray-700 overflow-auto max-h-20 mt-0.5">${esc(JSON.stringify(t.args || {}, null, 2))}</pre>`;
                        });
                        html += `</div>`;
                    }
                    if (showAgents && eAgents.length > 0) {
                        html += `<div class="space-y-1">`;
                        eAgents.forEach((r, ri) => {
                            const name = r.agent_name || 'unknown';
                            const found = (aAgents.map(a => a.agent_name || 'unknown')).includes(name);
                            const txt = (r.parts || []).map(p => p.text || '').join(' ');
                            html += `<div class="flex items-center gap-1 text-xs">
                                <span class="px-1.5 py-0.5 rounded bg-teal-100 dark:bg-teal-900/30 text-teal-700 dark:text-teal-300 font-medium">${esc(name)}</span>
                                ${found ? '<span class="text-green-600">found</span>' : '<span class="text-red-600 italic">missing</span>'}
                            </div>`;
                        });
                        html += `</div>`;
                    }
                    html += `</div>`;

                    html += `</div></div>`; // close grid + border
                }
                html += '</div></div>';
            }
        }

        if (html === '') {
            return '<p class="text-sm text-gray-500 dark:text-gray-400 italic">No details available</p>';
        }
        return html;
    }

    // ── Main execution detail view ───────────────────────────────────────

    function showExecutionDetails(index) {
        const exec = reportData.execution_runs[index];
        const mainContent = document.getElementById('main-content');
        const summaryBar = document.getElementById('summary-bar');
        const summaryText = document.getElementById('summary-text');

        // Summary bar
        const passedCount = exec.evaluator_results.filter(e => e.passed).length;
        const totalCount = exec.evaluator_results.length;
        const statusIcon = exec.overall_success ? '\u2713' : '\u2717';
        const statusColor = exec.overall_success ? 'text-green-600' : 'text-red-600';

        summaryText.innerHTML = `
            <span class="${statusColor} font-bold">${statusIcon}</span>
            <span class="ml-2">${esc(exec.eval_case_id)}</span>
            ${exec.source_path ? `<span class="mx-2">&bull;</span><span class="text-gray-400" title="${esc(exec.source_path)}"><span class="material-symbols-outlined text-xs align-middle">folder</span> ${esc(exec.source_path)}</span>` : ''}
            <span class="mx-2">&bull;</span>
            <span>${passedCount}/${totalCount} evaluators passed</span>
            <span class="mx-2">&bull;</span>
            <span>${exec.provider_result.time_taken.toFixed(2)}s</span>
            <span class="mx-2">&bull;</span>
            <span>$${exec.provider_result.cost.toFixed(4)}</span>
        `;
        summaryBar.classList.remove('hidden');

        // ── Evaluator Results Table ──────────────────────────────────────
        let evalHTML = `<div class="overflow-visible">
            <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
                <thead class="bg-gray-50 dark:bg-gray-900">
                    <tr>
                        <th class="px-4 py-2.5 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase">Evaluator</th>
                        <th class="px-4 py-2.5 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase">Status</th>
                        <th class="px-4 py-2.5 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase w-44">Score / Threshold</th>
                        <th class="px-4 py-2.5 text-center text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase w-24">Details</th>
                    </tr>
                </thead>
                <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">`;

        exec.evaluator_results.forEach((er, idx) => {
            const passed = er.passed;
            const icon = passed ? '\u2713' : '\u2717';
            const cls = passed ? 'text-green-600' : 'text-red-600';
            const bg = passed ? 'bg-green-50 dark:bg-green-900/20' : 'bg-red-50 dark:bg-red-900/20';
            const threshDisplay = er.threshold !== null ? er.threshold : '-';

            evalHTML += `
                <tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
                    <td class="px-4 py-2.5">
                        <div class="text-sm font-medium text-gray-900 dark:text-gray-100">${esc(er.evaluator_name)}</div>
                        <div class="text-xs text-gray-500 dark:text-gray-400">${esc(er.evaluator_type)}</div>
                    </td>
                    <td class="px-4 py-2.5">
                        <span class="${cls} font-bold inline-flex items-center px-2 py-0.5 rounded-full text-xs ${bg}">
                            ${icon} ${passed ? 'PASSED' : 'FAILED'}
                        </span>
                    </td>
                    <td class="px-4 py-2.5">
                        ${scoreBar(er.score, er.threshold)}
                        <div class="text-xs text-gray-500 mt-0.5">
                            ${er.score !== null ? `Score: ${er.score.toFixed(2)}` : ''}
                            ${er.score !== null && er.threshold !== null ? ' / ' : ''}
                            ${er.threshold !== null ? `Threshold: ${threshDisplay}` : ''}
                        </div>
                    </td>
                    <td class="px-4 py-2.5 text-center">
                        <button onclick="toggleEvalDetails(${idx})" id="eval-btn-${idx}"
                                class="text-primary hover:text-blue-700 text-xs font-medium">View Details</button>
                    </td>
                </tr>
                <tr id="eval-detail-${idx}" class="hidden">
                    <td colspan="4" class="p-0">
                        <div class="bg-gray-50 dark:bg-gray-900 px-5 py-4">
                            ${renderEvalDetail(er, exec)}
                        </div>
                    </td>
                </tr>`;
        });

        evalHTML += `</tbody></table></div>`;

        // ── Compact Conversation History (collapsible) ───────────────────
        const actualConv = exec.provider_result.conversation_history || [];
        const expectedConv = (exec.eval_case && exec.eval_case.conversation) || [];
        const maxInv = Math.max(actualConv.length, expectedConv.length);

        let convHTML = '';
        if (maxInv > 0) {
            convHTML += `<div class="space-y-2">`;
            for (let i = 0; i < maxInv; i++) {
                const inv = actualConv[i];
                const expInv = expectedConv[i];
                const userText = inv ? inv.user_content.parts.map(p => p.text || '').join(' ') : (expInv ? expInv.user_content.parts.map(p => p.text || '').join(' ') : '');
                const actualResp = inv ? inv.final_response.parts.map(p => p.text || '').join(' ') : '';
                const expectedResp = expInv ? expInv.final_response.parts.map(p => p.text || '').join(' ') : '';

                const aTools = inv && inv.intermediate_data ? inv.intermediate_data.tool_uses || [] : [];
                const eTools = expInv && expInv.intermediate_data ? expInv.intermediate_data.tool_uses || [] : [];
                const aAgents = inv && inv.intermediate_data ? inv.intermediate_data.intermediate_responses || [] : [];

                convHTML += `<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">`;

                // Turn header — user message as chat bubble
                convHTML += `<div class="bg-blue-50 dark:bg-blue-900/20 px-3 py-2 border-b border-gray-200 dark:border-gray-700">
                    <div class="flex items-center gap-2 mb-1">
                        <span class="text-xs font-bold text-gray-500 bg-gray-200 dark:bg-gray-700 px-1.5 py-0.5 rounded">Turn ${i + 1}</span>
                        <span class="material-symbols-outlined text-blue-600 text-xs">person</span>
                        <span class="text-xs font-semibold text-gray-600 dark:text-gray-400">User</span>
                        <span class="text-xs text-gray-400 ml-auto">${aTools.length > 0 ? aTools.length + ' tools' : ''}${aTools.length > 0 && aAgents.length > 0 ? ' &bull; ' : ''}${aAgents.length > 0 ? aAgents.length + ' sub-agents' : ''}</span>
                    </div>
                    <div class="text-xs text-gray-700 dark:text-gray-300 leading-relaxed prose-compact">${userText ? md(userText) : '<em class="text-gray-400">Empty</em>'}</div>
                </div>`;

                // Evaluator summary strip
                if (exec.evaluator_results && exec.evaluator_results.length > 0) {
                    convHTML += `<div class="bg-gray-50 dark:bg-gray-800/50 px-3 py-1.5 border-b border-gray-200 dark:border-gray-700 flex items-center gap-1.5 flex-wrap">`;
                    exec.evaluator_results.forEach(er => {
                        const icon = er.passed ? '\u2713' : '\u2717';
                        const bgCls = er.passed ? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 border-green-300 dark:border-green-700' : 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 border-red-300 dark:border-red-700';
                        const scoreStr = er.score !== null ? er.score.toFixed(2) : '';
                        convHTML += `<span class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded border ${bgCls} text-xs font-medium" title="${esc(er.evaluator_name)}${er.threshold !== null ? ' | Threshold: ' + er.threshold : ''}">
                            <span class="font-bold">${icon}</span>
                            <span>${esc(er.evaluator_name.replace('_evaluator', '').replace(/_/g, ' '))}</span>
                            ${scoreStr ? `<span class="opacity-75">${scoreStr}</span>` : ''}
                        </span>`;
                    });
                    convHTML += `</div>`;
                }

                // Side-by-side compact response (chat-style markdown)
                convHTML += `<div class="grid grid-cols-2 divide-x divide-gray-200 dark:divide-gray-700">`;

                // Actual (compact chat bubble)
                convHTML += `<div class="p-3">
                    <div class="flex items-center gap-1 mb-1"><span class="material-symbols-outlined text-purple-600 text-xs">smart_toy</span><span class="text-xs font-semibold text-gray-500">Actual</span>
                        ${aTools.length > 0 ? `<span class="text-xs text-purple-500 ml-1">${aTools.length} tools</span>` : ''}
                        ${aAgents.length > 0 ? `<span class="text-xs text-orange-500 ml-1">${aAgents.length} agents</span>` : ''}
                    </div>
                    <div class="bg-white dark:bg-gray-800 p-2.5 rounded-lg border border-gray-200 dark:border-gray-700 text-xs leading-relaxed overflow-auto prose-compact">${actualResp ? md(actualResp) : '<em class="text-gray-400">No response</em>'}</div>`;
                convHTML += `</div>`;

                // Expected (compact chat bubble)
                convHTML += `<div class="p-3">
                    <div class="flex items-center gap-1 mb-1"><span class="material-symbols-outlined text-green-600 text-xs">check_circle</span><span class="text-xs font-semibold text-gray-500">Expected</span>
                        ${eTools.length > 0 ? `<span class="text-xs text-green-500 ml-1">${eTools.length} tools</span>` : ''}
                    </div>
                    <div class="bg-white dark:bg-gray-800 p-2.5 rounded-lg border border-gray-200 dark:border-gray-700 text-xs leading-relaxed overflow-auto prose-compact">${expectedResp && expectedResp.trim() ? md(expectedResp.trim()) : '<em class="text-gray-400">Not defined</em>'}</div>`;
                convHTML += `</div>`;

                convHTML += `</div>`; // close grid

                // "View Full Response" expandable section
                const hasExtra = actualResp.length > 200 || aTools.length > 0 || aAgents.length > 0 || eTools.length > 0;
                if (hasExtra) {
                    convHTML += `<div class="border-t border-gray-200 dark:border-gray-700">
                        <button onclick="toggleConvFull(${i})" id="conv-full-btn-${i}" class="w-full px-3 py-1.5 text-xs text-primary hover:text-blue-700 font-medium flex items-center justify-center gap-1 hover:bg-gray-50 dark:hover:bg-gray-700/50">
                            <span class="material-symbols-outlined text-xs">unfold_more</span> View Full Response
                        </button>
                    </div>`;
                    convHTML += `<div id="conv-full-${i}" class="hidden border-t border-gray-200 dark:border-gray-700">`;

                    // Full actual response
                    convHTML += `<div class="p-4 space-y-3">
                        <div class="flex items-center gap-1 mb-1"><span class="material-symbols-outlined text-purple-600 text-sm">smart_toy</span><span class="text-sm font-semibold text-gray-700 dark:text-gray-300">Full Actual Response</span></div>
                        <pre class="bg-white dark:bg-gray-800 p-3 rounded border border-gray-200 dark:border-gray-700 text-xs whitespace-pre-wrap overflow-auto max-h-96">${esc(actualResp) || '<em class="text-gray-400">No response</em>'}</pre>`;

                    // Tools with args
                    if (aTools.length > 0) {
                        convHTML += `<div>
                            <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-2 uppercase">Tools (${aTools.length})</div>
                            <div class="space-y-2">`;
                        aTools.forEach((t, ti) => {
                            const eToolNames = eTools.map(et => et.name);
                            const found = eToolNames.includes(t.name);
                            const argsStr = JSON.stringify(t.args || {}, null, 2);
                            const resultStr = t.result !== undefined && t.result !== null ? (typeof t.result === 'object' ? JSON.stringify(t.result, null, 2) : String(t.result)) : null;

                            convHTML += `<div class="bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 overflow-hidden">
                                <div class="flex items-center gap-2 px-3 py-1.5 bg-gray-50 dark:bg-gray-700/50 border-b border-gray-200 dark:border-gray-700">
                                    <span class="px-1.5 py-0.5 rounded bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 text-xs font-bold">${esc(t.name)}</span>
                                    ${eTools.length > 0 ? (found ? '<span class="text-xs text-green-600">\u2713 expected</span>' : '<span class="text-xs text-yellow-600">unexpected</span>') : ''}
                                    ${t.score !== undefined && t.score !== null ? `<span class="text-xs text-gray-500 ml-auto">score: <span class="font-semibold">${typeof t.score === 'number' ? t.score.toFixed(2) : t.score}</span></span>` : ''}
                                </div>
                                <div class="p-2">
                                    <div class="text-xs text-gray-500 mb-1">Arguments</div>
                                    <pre class="text-xs bg-gray-50 dark:bg-gray-900 p-2 rounded overflow-auto max-h-48 whitespace-pre-wrap">${esc(argsStr)}</pre>
                                </div>
                                ${resultStr ? `<div class="p-2 border-t border-gray-100 dark:border-gray-700">
                                    <div class="text-xs text-gray-500 mb-1">Result</div>
                                    <pre class="text-xs bg-gray-50 dark:bg-gray-900 p-2 rounded overflow-auto max-h-48 whitespace-pre-wrap">${esc(resultStr)}</pre>
                                </div>` : ''}
                            </div>`;
                        });
                        convHTML += `</div></div>`;
                    }

                    // Sub-agents with full responses
                    if (aAgents.length > 0) {
                        convHTML += `<div>
                            <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-2 uppercase">Sub-Agents (${aAgents.length})</div>
                            <div class="space-y-2">`;
                        aAgents.forEach((r, ri) => {
                            const name = r.agent_name || 'unknown';
                            const txt = (r.parts || []).map(p => p.text || '').join(' ');
                            const agentTools = r.tool_uses || [];

                            convHTML += `<div class="bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 overflow-hidden">
                                <div class="flex items-center gap-2 px-3 py-1.5 bg-gray-50 dark:bg-gray-700/50 border-b border-gray-200 dark:border-gray-700">
                                    <span class="px-1.5 py-0.5 rounded bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 text-xs font-bold">${esc(name)}</span>
                                    ${agentTools.length > 0 ? `<span class="text-xs text-gray-400">${agentTools.length} tools</span>` : ''}
                                </div>
                                ${txt ? `<div class="p-2">
                                    <div class="text-xs text-gray-500 mb-1">Response</div>
                                    <pre class="text-xs bg-gray-50 dark:bg-gray-900 p-2 rounded overflow-auto max-h-48 whitespace-pre-wrap">${esc(txt)}</pre>
                                </div>` : ''}
                                ${agentTools.length > 0 ? `<div class="p-2 border-t border-gray-100 dark:border-gray-700">
                                    <div class="text-xs text-gray-500 mb-1">Tools</div>
                                    <div class="space-y-1">
                                        ${agentTools.map(at => `<div class="flex items-center gap-1 text-xs"><span class="px-1 py-0.5 rounded bg-purple-50 dark:bg-purple-900/20 text-purple-600 dark:text-purple-400 font-medium">${esc(at.name)}</span></div>`).join('')}
                                    </div>
                                </div>` : ''}
                            </div>`;
                        });
                        convHTML += `</div></div>`;
                    }

                    // Expected tools comparison
                    if (eTools.length > 0) {
                        convHTML += `<div>
                            <div class="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-2 uppercase">Expected Tools (${eTools.length})</div>
                            <div class="flex flex-wrap gap-1.5">`;
                        eTools.forEach(t => {
                            const found = aTools.map(a => a.name).includes(t.name);
                            convHTML += `<span class="inline-flex items-center gap-1 px-2 py-1 rounded border ${found ? 'bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 border-green-300 dark:border-green-700' : 'bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 border-red-300 dark:border-red-700'} text-xs font-medium">${esc(t.name)} ${found ? '\u2713' : '\u2717 missing'}</span>`;
                        });
                        convHTML += `</div></div>`;
                    }

                    convHTML += `</div></div>`; // close full section
                }

                convHTML += `</div>`; // close border card
            }
            convHTML += `</div>`;
        }

        // ── Assemble main content ────────────────────────────────────────
        mainContent.innerHTML = `
            <div class="max-w-7xl mx-auto space-y-4">
                <!-- Evaluator Results -->
                <div class="bg-white dark:bg-gray-800 rounded-lg shadow">
                    <div class="px-5 py-3 border-b border-gray-200 dark:border-gray-700">
                        <h3 class="text-lg font-bold">Evaluator Results</h3>
                    </div>
                    ${evalHTML}
                </div>

                <!-- Conversation History (collapsible) -->
                ${maxInv > 0 ? `
                <div class="bg-white dark:bg-gray-800 rounded-lg shadow">
                    <button onclick="toggleSection('conv-section')" id="conv-section-btn"
                            class="w-full px-5 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700/50 rounded-t-lg">
                        <h3 class="text-lg font-bold">Conversation History</h3>
                        <span class="text-gray-400 text-sm"><span class="arrow">\u25BC</span></span>
                    </button>
                    <div id="conv-section" class="p-5">
                        ${convHTML}
                    </div>
                </div>` : ''}
            </div>
        `;
    }

    document.addEventListener('DOMContentLoaded', initializeReport);
</script>
</body>
</html>
