<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Agent Bus{% endblock %}</title>

    <!-- DaisyUI + Tailwind -->
    <link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet" type="text/css" />
    <script src="https://cdn.tailwindcss.com"></script>

    <!-- HTMX -->
    <script src="https://unpkg.com/htmx.org@2.0.4"></script>

    <!-- Marked.js for markdown rendering -->
    <script src="https://cdn.jsdelivr.net/npm/marked@15.0.12/marked.min.js"></script>

    <!-- Highlight.js for syntax highlighting -->
    <link id="hljs-theme-dark" rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css" disabled>
    <link id="hljs-theme-light" rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" disabled>
    <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>

    <!-- DOMPurify for HTML sanitization -->
    <script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>

    <!-- Mermaid.js for diagram rendering (lazy-loaded) -->
    <script>
        let mermaidLoaded = false;
        let mermaidLoadPromise = null;

        function loadMermaid() {
            if (mermaidLoadPromise) return mermaidLoadPromise;
            mermaidLoadPromise = new Promise((resolve, reject) => {
                const script = document.createElement('script');
                script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10.9.3/dist/mermaid.min.js';
                script.onload = () => {
                    mermaidLoaded = true;
                    initMermaid();
                    resolve();
                };
                script.onerror = (e) => {
                    mermaidLoadPromise = null;
                    reject(e);
                };
                document.head.appendChild(script);
            });
            return mermaidLoadPromise;
        }

        function getMermaidTheme() {
            const theme = document.documentElement.getAttribute('data-theme');
            return theme === 'dark' ? 'dark' : 'default';
        }

        function initMermaid() {
            if (typeof mermaid === 'undefined') return;
            mermaid.initialize({
                startOnLoad: false,
                theme: getMermaidTheme(),
                securityLevel: 'strict',
                flowchart: { htmlLabels: false },
                sequence: { useMaxWidth: true },
            });
        }

        async function renderMermaidBlocks(container) {
            try {
                const blocks = (container || document).querySelectorAll(
                    '.markdown-content pre code.language-mermaid:not([data-mermaid-processed])'
                );
                if (blocks.length === 0) return;

                // Lazy-load mermaid if needed
                if (!mermaidLoaded) {
                    try {
                        await loadMermaid();
                    } catch (e) {
                        console.warn('Mermaid load error:', e);
                        return;
                    }
                }

                blocks.forEach((block) => {
                    const pre = block.parentElement;
                    if (!pre) return;

                    block.setAttribute('data-mermaid-processed', 'true');
                    const source = block.textContent || '';

                    // Create container with source stored for theme re-rendering
                    const wrapper = document.createElement('div');
                    wrapper.className = 'mermaid-wrapper';
                    wrapper.setAttribute('data-mermaid-source', source);

                    const diagramDiv = document.createElement('div');
                    diagramDiv.className = 'mermaid';
                    diagramDiv.textContent = source;
                    wrapper.appendChild(diagramDiv);

                    pre.replaceWith(wrapper);
                });

                // Render all mermaid diagrams
                try {
                    await mermaid.run();
                } catch (e) {
                    console.warn('Mermaid render error:', e);
                }
            } catch (e) {
                console.warn('Mermaid processing error:', e);
            }
        }

        async function reRenderAllMermaid() {
            if (!mermaidLoaded) return;
            try {
                // Re-init with new theme
                initMermaid();

                // Find all mermaid wrappers and re-render
                const wrappers = document.querySelectorAll(
                    '.mermaid-wrapper[data-mermaid-source]'
                );
                if (wrappers.length === 0) return;

                wrappers.forEach((wrapper) => {
                    const source = wrapper.getAttribute('data-mermaid-source') || '';
                    wrapper.innerHTML = '';
                    const diagramDiv = document.createElement('div');
                    diagramDiv.className = 'mermaid';
                    diagramDiv.textContent = source;
                    wrapper.appendChild(diagramDiv);
                });

                try {
                    await mermaid.run();
                } catch (e) {
                    console.warn('Mermaid re-render error:', e);
                }
            } catch (e) {
                console.warn('Mermaid re-render processing error:', e);
            }
        }
    </script>

    <!-- Theme (system + user override) + hljs theme sync -->
    <script>
        const THEME_STORAGE_KEY = 'agent_bus_theme';

        function syncHljsTheme(isDark) {
            const darkTheme = document.getElementById('hljs-theme-dark');
            const lightTheme = document.getElementById('hljs-theme-light');
            if (darkTheme && lightTheme) {
                darkTheme.disabled = !isDark;
                lightTheme.disabled = isDark;
            }
        }

        function getStoredTheme() {
            try {
                const theme = localStorage.getItem(THEME_STORAGE_KEY);
                return theme === 'dark' || theme === 'light' ? theme : null;
            } catch (e) {
                return null;
            }
        }

        function getSystemTheme() {
            return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
        }

        function setTheme(theme, persist = false) {
            document.documentElement.setAttribute('data-theme', theme);
            syncHljsTheme(theme === 'dark');

            const toggle = document.getElementById('theme-toggle');
            if (toggle) toggle.checked = theme === 'dark';

            // Re-render mermaid diagrams with new theme
            reRenderAllMermaid();

            if (persist) {
                try {
                    localStorage.setItem(THEME_STORAGE_KEY, theme);
                } catch (e) {}
            }
        }

        function initTheme() {
            setTheme(getStoredTheme() ?? getSystemTheme(), false);
        }

        // Follow system changes only when no user override exists.
        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
            if (getStoredTheme()) return;
            setTheme(e.matches ? 'dark' : 'light', false);
        });

        // Initialize theme immediately to avoid FOUC.
        initTheme();
    </script>

    <style>
        * {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
        }
        code, pre, .font-mono {
            font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Mono', 'Droid Sans Mono', 'Source Code Pro', monospace;
        }

        /* View Transitions for smoother content swaps */
        ::view-transition-old(root),
        ::view-transition-new(root) {
            animation-duration: 150ms;
        }

        ::-webkit-scrollbar { width: 8px; height: 8px; }
        ::-webkit-scrollbar-track { background: oklch(var(--b1)); }
        ::-webkit-scrollbar-thumb { background: oklch(var(--bc) / 0.15); border-radius: 4px; }
        ::-webkit-scrollbar-thumb:hover { background: oklch(var(--bc) / 0.25); }

	        .message-item {
	            border-bottom: 1px solid oklch(var(--bc) / 0.08);
	            transition: background-color 0.1s ease;
	        }
	        .message-item:hover {
	            background-color: oklch(var(--bc) / 0.04);
	        }

	        .message-item:target {
	            outline: 2px solid oklch(var(--bc) / 0.4);
	            outline-offset: 2px;
	            scroll-margin-top: 64px;
	        }

	        .thread-toggle[aria-expanded="true"] .thread-chevron {
	            transform: rotate(90deg);
	        }

	        .message-card {
	            background-color: oklch(var(--bc) / 0.03);
	            border: 1px solid oklch(var(--bc) / 0.12);
	            border-left: 4px solid oklch(var(--bc) / 0.25);
	            border-radius: 4px;
	            margin: 0.75rem 0;
	            transition: border-color 0.15s ease, background-color 0.15s ease;
	        }
	        .message-card:hover {
	            background-color: oklch(var(--bc) / 0.05);
	            border-color: oklch(var(--bc) / 0.2);
	            border-left-color: oklch(var(--bc) / 0.4);
	        }

        /* Threaded view indentation */
        .message-item[data-depth="1"] { margin-left: 2.5rem; }
        .message-item[data-depth="2"] { margin-left: 5rem; }
        .message-item[data-depth="3"] { margin-left: 7.5rem; }

        .presence-dot {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background-color: hsl(140 50% 50%);
            animation: pulse-dot 2s ease-in-out infinite;
        }

        @keyframes pulse-dot {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        /* Markdown rendered content styles */
        .markdown-content h1 { font-size: 1.25rem; font-weight: 700; margin: 0.75rem 0 0.5rem; }
        .markdown-content h2 { font-size: 1.1rem; font-weight: 600; margin: 0.6rem 0 0.4rem; }
        .markdown-content h3 { font-size: 1rem; font-weight: 600; margin: 0.5rem 0 0.3rem; }
        .markdown-content p { margin: 0.4rem 0; }
        .markdown-content ul, .markdown-content ol { margin: 0.4rem 0; padding-left: 1.5rem; }
	        .markdown-content li { margin: 0.2rem 0; }
	        .markdown-content code {
	            background: hsl(30 50% 50% / 0.12);
	            padding: 0.15rem 0.5rem;
	            border-radius: 4px;
	            font-size: 0.85em;
	        }
	        .markdown-content pre {
	            background: oklch(var(--bc) / 0.06);
	            border: 1px solid oklch(var(--bc) / 0.1);
	            padding: 1rem;
	            border-radius: 6px;
	            overflow-x: auto;
	            scrollbar-width: thin;
	            margin: 0.5rem 0;
	            font-size: 0.8rem;
	            line-height: 1.5;
	        }
	        .markdown-content pre code {
	            background: none;
	            padding: 0;
	        }
	        .markdown-content a {
	            color: hsl(210 100% 50%);
	            text-decoration: none;
	        }
	        .markdown-content a:hover {
	            text-decoration: underline;
	        }
	        .markdown-content blockquote {
	            border-left: 3px solid hsl(210 100% 50% / 0.5);
	            background: oklch(var(--bc) / 0.03);
	            padding: 0.5rem 1rem;
	            margin: 0.75rem 0;
	        }
        .markdown-content strong { font-weight: 600; }
        .markdown-content em { font-style: italic; }
        .markdown-content hr { border: none; border-top: 1px solid oklch(var(--bc) / 0.15); margin: 0.75rem 0; }

        /* Source view */
        .message-source {
            background: oklch(var(--bc) / 0.05);
            padding: 0.5rem;
            border-radius: 4px;
            white-space: pre-wrap;
            word-break: break-word;
        }

        /* Mermaid diagrams */
        .mermaid-wrapper {
            margin: 0.5rem 0;
            padding: 1rem;
            background: oklch(var(--bc) / 0.03);
            border: 1px solid oklch(var(--bc) / 0.1);
            border-radius: 6px;
            overflow-x: auto;
        }
        .mermaid-wrapper svg {
            max-width: 100%;
            height: auto;
        }
    </style>

    <script>
        // Configure marked
        marked.setOptions({
            breaks: true,
            gfm: true,
        });

        // Highlight fenced code blocks (best-effort).
        function highlightCode(container) {
            if (typeof hljs === 'undefined') return;

            const blocks = (container || document).querySelectorAll(
                '.markdown-content pre code:not([data-highlighted])'
            );
            blocks.forEach(block => {
                try {
                    hljs.highlightElement(block);
                } catch (e) {}
            });
        }

        // Render markdown in all .markdown-content elements
        function renderMarkdown(container) {
            const elements = (container || document).querySelectorAll(
                '.markdown-content[data-raw]:not([data-rendered])'
            );
            elements.forEach(el => {
                const raw = el.getAttribute('data-raw');
                if (raw !== null) {
                    // Parse markdown and sanitize HTML to prevent XSS
                    const html = marked.parse(raw);
                    el.innerHTML = DOMPurify.sanitize(html);
                    el.setAttribute('data-rendered', 'true');
                }
            });
            highlightCode(container);
            renderMermaidBlocks(container);
        }

        // Toggle between rendered and source view
        function toggleSource(messageId) {
            const rendered = document.querySelector(`#msg-${messageId} .message-rendered`);
            const source = document.querySelector(`#msg-${messageId} .message-source`);
            const btn = document.querySelector(`#msg-${messageId} .toggle-source-btn`);

            if (source.classList.contains('hidden')) {
                source.classList.remove('hidden');
                rendered.classList.add('hidden');
                btn.title = 'Show rendered';
            } else {
                source.classList.add('hidden');
                rendered.classList.remove('hidden');
                btn.title = 'Show source';
            }
        }

        // Copy source to clipboard
        function copySource(messageId) {
            const source = document.querySelector(`#msg-${messageId} .message-source`);
            const text = source.textContent;
            navigator.clipboard.writeText(text).then(() => {
                const btn = document.querySelector(`#msg-${messageId} .copy-source-btn`);
                const originalTitle = btn.title;
                btn.title = 'Copied!';
                setTimeout(() => { btn.title = originalTitle; }, 1500);
            });
        }

        function openTopicsDrawer() {
            const drawer = document.getElementById('topics-drawer');
            if (drawer) drawer.checked = true;
        }

        function closeTopicsDrawer() {
            const drawer = document.getElementById('topics-drawer');
            if (drawer) drawer.checked = false;
        }

        function focusTopicSearch() {
            const search = document.getElementById('topic-search');
            if (!search) return;
            openTopicsDrawer();
            search.focus();
            search.select();
        }

        function filterTopics() {
            const search = document.getElementById('topic-search');
            const query = (search?.value || '').trim().toLowerCase();
            document.querySelectorAll('[data-topic-row]').forEach(row => {
                const hay = (row.getAttribute('data-topic-hay') || '').toLowerCase();
                const visible = query === '' || hay.includes(query);
                row.classList.toggle('hidden', !visible);
            });
        }

	        function getMainScroll() {
	            return document.getElementById('main-scroll');
	        }

	        function initGlobalControls() {
	            // Topic search filter
	            const search = document.getElementById('topic-search');
	            if (search && !search.dataset.bound) {
	                search.addEventListener('input', filterTopics);
	                search.dataset.bound = 'true';
	            }

	            const themeToggle = document.getElementById('theme-toggle');
	            if (themeToggle && !themeToggle.dataset.bound) {
	                themeToggle.checked = document.documentElement.getAttribute('data-theme') === 'dark';
	                themeToggle.addEventListener('change', () => {
	                    setTheme(themeToggle.checked ? 'dark' : 'light', true);
	                });
	                themeToggle.dataset.bound = 'true';
	            }
	        }

	        // ============================================
	        // Topic detail page functionality
	        // ============================================

	        let currentTopicId = null;
	        let selectMode = false;
	        const selectedMessages = new Set();

	        // Map of parentId -> [childIds] for reply tree
	        let threadTree = new Map();
	        // Set of collapsed thread root IDs
	        const collapsedThreads = new Set();

	        // Threaded view
	        let threadedView = false;
	        let previousThreadOrder = [];

	        function isTopicDetailPage() {
	            return Boolean(document.getElementById('messages')) && Boolean(document.getElementById('topic-id'));
	        }

	        function getTopicId() {
	            return document.getElementById('topic-id')?.value || null;
	        }

	        function resetTopicDetailState() {
	            currentTopicId = getTopicId();
	            selectMode = false;
	            selectedMessages.clear();
	            threadTree = new Map();
	            collapsedThreads.clear();
	            threadedView = false;
	            previousThreadOrder = [];

	            document.body.classList.remove('select-mode');
	            const selectionBar = document.getElementById('selection-bar');
	            if (selectionBar) selectionBar.classList.add('hidden');

	            document.querySelectorAll('.message-checkbox').forEach(cb => {
	                cb.classList.add('hidden');
	            });

	            const viewBtn = document.getElementById('view-mode-btn');
	            if (viewBtn) viewBtn.classList.remove('btn-active');
	            const viewLabel = document.getElementById('view-mode-label');
	            if (viewLabel) viewLabel.textContent = 'Flat';
	        }

	        function initTopicDetailPage() {
	            if (!isTopicDetailPage()) {
	                currentTopicId = null;
	                return;
	            }

	            const topicId = getTopicId();
	            if (topicId !== currentTopicId) {
	                resetTopicDetailState();
	            }

	            buildThreadTree();
	            if (threadedView) {
	                renderThreadedView(false);
	            }
	        }

	        function toggleSelectMode() {
	            selectMode = !selectMode;
	            document.body.classList.toggle('select-mode', selectMode);
	            document.getElementById('select-mode-btn')?.classList.toggle('btn-active', selectMode);

	            // Show/hide checkboxes
	            document.querySelectorAll('.message-checkbox').forEach(cb => {
	                cb.classList.toggle('hidden', !selectMode);
	            });

	            // Clear selection when exiting select mode
	            if (!selectMode) {
	                clearSelection();
	            }
	        }

	        function toggleMessageSelection(messageId, checkbox) {
	            if (checkbox.checked) {
	                selectedMessages.add(messageId);
	            } else {
	                selectedMessages.delete(messageId);
	            }
	            updateSelectionUI();
	        }

	        function updateSelectionUI() {
	            const count = selectedMessages.size;
	            const bar = document.getElementById('selection-bar');
	            const countEl = document.getElementById('selection-count');

	            if (!bar || !countEl) return;

	            if (count > 0) {
	                bar.classList.remove('hidden');
	                countEl.textContent = `${count} selected`;
	            } else {
	                bar.classList.add('hidden');
	            }
	        }

	        function clearSelection() {
	            selectedMessages.clear();
	            document.querySelectorAll('.message-checkbox input').forEach(cb => {
	                cb.checked = false;
	            });
	            updateSelectionUI();
	        }

	        function deleteSelected() {
	            if (selectedMessages.size === 0) return;

	            const count = selectedMessages.size;
	            if (
	                !confirm(
	                    `Delete ${count} message${count > 1 ? 's' : ''} and their replies? This cannot be undone.`
	                )
	            ) {
	                return;
	            }

	            const topicId = getTopicId();
	            if (!topicId) return;
	            const messageIds = Array.from(selectedMessages);

	            fetch(`/topics/${topicId}/messages`, {
	                method: 'DELETE',
	                headers: { 'Content-Type': 'application/json' },
	                body: JSON.stringify(messageIds),
	            })
	                .then(response => response.json())
	                .then(data => {
	                    // Remove deleted messages from DOM
	                    const deletedIds = data.deleted_message_ids || messageIds;
	                    deletedIds.forEach(id => {
	                        const el = document.getElementById(`msg-${id}`);
	                        if (el) el.remove();
	                    });
	                    clearSelection();
	                    // Exit select mode
	                    if (selectMode) toggleSelectMode();
	                    initTopicDetailPage();
	                })
	                .catch(err => {
	                    console.error('Delete failed:', err);
	                    alert('Failed to delete messages');
	                });
	        }

	        // ============================================
	        // Thread collapse/expand functionality
	        // ============================================

	        function setThreadExpanded(messageId, expanded) {
	            const parentEl = document.getElementById(`msg-${messageId}`);
	            if (!parentEl) return;
	            const toggle = parentEl.querySelector('.thread-toggle');
	            if (toggle) {
	                toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
	            }
	        }

	        function buildThreadTree() {
	            threadTree = new Map();
	            const messages = document.querySelectorAll('#messages .message-item');

	            // Reset UI state before rebuilding
	            messages.forEach(msg => {
	                const toggle = msg.querySelector('.thread-toggle');
	                const badge = msg.querySelector('.reply-count');
	                if (toggle) {
	                    toggle.classList.add('hidden');
	                    toggle.setAttribute('aria-expanded', 'true');
	                }
	                if (badge) {
	                    badge.textContent = '';
	                    badge.classList.add('hidden');
	                }
	            });

	            messages.forEach(msg => {
	                const replyTo = msg.dataset.replyTo;
	                if (replyTo) {
	                    if (!threadTree.has(replyTo)) {
	                        threadTree.set(replyTo, []);
	                    }
	                    threadTree.get(replyTo).push(msg.id.replace('msg-', ''));
	                }
	            });

	            // Update UI: show toggles for messages with replies
	            let hasThreads = false;
	            threadTree.forEach((children, parentId) => {
	                const parentEl = document.getElementById(`msg-${parentId}`);
	                if (parentEl && children.length > 0) {
	                    hasThreads = true;
	                    const toggle = parentEl.querySelector('.thread-toggle');
	                    const badge = parentEl.querySelector('.reply-count');
	                    if (toggle) toggle.classList.remove('hidden');
	                    if (badge) {
	                        const count = countAllDescendants(parentId);
	                        badge.textContent = `(${count} repl${count === 1 ? 'y' : 'ies'})`;
	                        badge.classList.remove('hidden');
	                    }
	                    setThreadExpanded(parentId, !collapsedThreads.has(parentId));
	                }
	            });

	            // Show/hide thread controls in header
	            const controls = document.getElementById('thread-controls');
	            if (controls) {
	                controls.classList.toggle('hidden', !hasThreads);
	            }

	            applyCollapsedState();
	        }

	        function countAllDescendants(messageId) {
	            const children = threadTree.get(messageId) || [];
	            let count = children.length;
	            children.forEach(childId => {
	                count += countAllDescendants(childId);
	            });
	            return count;
	        }

	        function getDescendantIds(messageId) {
	            const result = [];
	            const children = threadTree.get(messageId) || [];
	            children.forEach(childId => {
	                result.push(childId);
	                result.push(...getDescendantIds(childId));
	            });
	            return result;
	        }

	        function toggleThread(messageId) {
	            if (collapsedThreads.has(messageId)) {
	                expandThread(messageId);
	            } else {
	                collapseThread(messageId);
	            }
	        }

	        function collapseThread(messageId) {
	            collapsedThreads.add(messageId);
	            const descendants = getDescendantIds(messageId);
	            descendants.forEach(id => {
	                const el = document.getElementById(`msg-${id}`);
	                if (el) el.classList.add('hidden');
	            });
	            setThreadExpanded(messageId, false);
	        }

	        function expandThread(messageId) {
	            collapsedThreads.delete(messageId);
	            const descendants = getDescendantIds(messageId);
	            descendants.forEach(id => {
	                const el = document.getElementById(`msg-${id}`);
	                if (el) el.classList.remove('hidden');
	            });
	            setThreadExpanded(messageId, true);
	            // Preserve nested collapsed threads.
	            applyCollapsedState();
	        }

	        function applyCollapsedState() {
	            Array.from(collapsedThreads).forEach(messageId => {
	                // Only apply if the message exists and has known children.
	                if (!document.getElementById(`msg-${messageId}`)) return;
	                const children = threadTree.get(messageId) || [];
	                if (children.length === 0) return;

	                const descendants = getDescendantIds(messageId);
	                descendants.forEach(id => {
	                    const el = document.getElementById(`msg-${id}`);
	                    if (el) el.classList.add('hidden');
	                });
	                setThreadExpanded(messageId, false);
	            });
	        }

	        function collapseAllThreads() {
	            threadTree.forEach((children, parentId) => {
	                if (children.length > 0 && document.getElementById(`msg-${parentId}`)) {
	                    collapseThread(parentId);
	                }
	            });
	        }

	        function expandAllThreads() {
	            collapsedThreads.clear();
	            document.querySelectorAll('#messages .message-item.hidden').forEach(el => {
	                el.classList.remove('hidden');
	            });
	            buildThreadTree();
	        }

	        // ============================================
	        // Threaded view functionality
	        // ============================================

	        function arraysEqual(a, b) {
	            if (a.length !== b.length) return false;
	            for (let i = 0; i < a.length; i++) {
	                if (a[i] !== b[i]) return false;
	            }
	            return true;
	        }

	        function toggleViewMode() {
	            threadedView = !threadedView;
	            document.getElementById('view-mode-btn')?.classList.toggle('btn-active', threadedView);
	            const label = document.getElementById('view-mode-label');
	            if (label) label.textContent = threadedView ? 'Threaded' : 'Flat';

	            if (threadedView) {
	                renderThreadedView(true); // Force render on explicit toggle
	            } else {
	                renderFlatView();
	            }
	        }

	        function getTopVisibleMessage() {
	            const container = document.getElementById('main-scroll');
	            if (!container) return null;
	            const messages = document.querySelectorAll('#messages .message-item:not(.hidden)');
	            const containerRect = container.getBoundingClientRect();

	            for (const msg of messages) {
	                const rect = msg.getBoundingClientRect();
	                if (rect.top >= containerRect.top && rect.top < containerRect.bottom) {
	                    return msg.id;
	                }
	            }
	            return null;
	        }

	        function scrollToMessage(msgId) {
	            if (!msgId) return;
	            const el = document.getElementById(msgId);
	            if (el) {
	                el.scrollIntoView({ block: 'start', behavior: 'auto' });
	            }
	        }

	        function getMessageDepth(messageId, visited = new Set()) {
	            // Prevent infinite loops from circular references
	            if (visited.has(messageId)) return 0;
	            visited.add(messageId);

	            const el = document.getElementById(`msg-${messageId}`);
	            if (!el) return 0;

	            const replyTo = el.dataset.replyTo;
	            if (!replyTo) return 0;

	            // Check if parent exists in DOM
	            const parentEl = document.getElementById(`msg-${replyTo}`);
	            if (!parentEl) return 0; // Treat as root if parent not loaded

	            return Math.min(1 + getMessageDepth(replyTo, visited), 3); // Cap at 3
	        }

	        function getMessageSeq(messageId) {
	            const el = document.getElementById(`msg-${messageId}`);
	            if (!el) return 0;
	            const seq = Number.parseInt(el.dataset.seq ?? '', 10);
	            return Number.isFinite(seq) ? seq : 0;
	        }

	        function buildThreadedOrder() {
	            const messages = document.querySelectorAll('#messages .message-item');
	            const messageIds = [];
	            const childrenMap = new Map(); // parentId -> [childIds sorted by seq]
	            const roots = [];

	            // Build maps
	            messages.forEach(msg => {
	                const msgId = msg.id.replace('msg-', '');
	                messageIds.push(msgId);
	                const replyTo = msg.dataset.replyTo;

	                if (replyTo && document.getElementById(`msg-${replyTo}`)) {
	                    // Has a parent that exists in DOM
	                    if (!childrenMap.has(replyTo)) {
	                        childrenMap.set(replyTo, []);
	                    }
	                    childrenMap.get(replyTo).push(msgId);
	                } else {
	                    // Root message (no reply_to or parent not loaded)
	                    roots.push(msgId);
	                }
	            });

	            // Sort roots by seq
	            roots.sort((a, b) => getMessageSeq(a) - getMessageSeq(b));

	            // Sort children by seq
	            childrenMap.forEach(children => {
	                children.sort((a, b) => getMessageSeq(a) - getMessageSeq(b));
	            });

	            // Build ordered list: DFS from roots
	            const ordered = [];
	            const visited = new Set();
	            function addWithChildren(msgId) {
	                if (visited.has(msgId)) return;
	                visited.add(msgId);
	                ordered.push(msgId);
	                const children = childrenMap.get(msgId) || [];
	                children.forEach(addWithChildren);
	            }
	            roots.forEach(addWithChildren);

	            // Ensure all messages are included (handles cycles / missing roots)
	            messageIds.sort((a, b) => getMessageSeq(a) - getMessageSeq(b));
	            messageIds.forEach(addWithChildren);

	            return ordered;
	        }

	        function renderThreadedView(forceRender = false) {
	            const orderedIds = buildThreadedOrder();

	            // Skip re-render if order unchanged (unless forced)
	            // This prevents scroll jumps during polling when no reordering is needed
	            if (!forceRender && arraysEqual(orderedIds, previousThreadOrder)) {
	                // Just ensure new messages have correct depth attributes
	                orderedIds.forEach(msgId => {
	                    const el = document.getElementById(`msg-${msgId}`);
	                    if (el && !el.hasAttribute('data-depth')) {
	                        const depth = getMessageDepth(msgId);
	                        if (depth > 0) el.setAttribute('data-depth', depth);
	                    }
	                });
	                previousThreadOrder = orderedIds;
	                return;
	            }

	            const anchorId = getTopVisibleMessage();
	            const messagesContainer = document.getElementById('messages');
	            if (!messagesContainer) return;

	            // Calculate depths FIRST while all elements are still in DOM
	            // (appendChild removes elements from DOM, breaking parent lookups)
	            const depthMap = new Map();
	            orderedIds.forEach(msgId => {
	                depthMap.set(msgId, getMessageDepth(msgId));
	            });

	            // Build fragment with ordered messages
	            const fragment = document.createDocumentFragment();
	            orderedIds.forEach(msgId => {
	                const el = document.getElementById(`msg-${msgId}`);
	                if (el) {
	                    // Set depth attribute from pre-calculated map
	                    const depth = depthMap.get(msgId);
	                    if (depth > 0) {
	                        el.setAttribute('data-depth', depth);
	                    } else {
	                        el.removeAttribute('data-depth');
	                    }
	                    fragment.appendChild(el);
	                }
	            });

	            // Replace children
	            messagesContainer.innerHTML = '';
	            messagesContainer.appendChild(fragment);

	            // Restore scroll position
	            scrollToMessage(anchorId);

	            // Re-apply collapse state
	            applyCollapsedState();

	            // Remember order for next comparison
	            previousThreadOrder = orderedIds;
	        }

	        function renderFlatView() {
	            const anchorId = getTopVisibleMessage();
	            const messagesContainer = document.getElementById('messages');
	            if (!messagesContainer) return;
	            const messages = Array.from(document.querySelectorAll('#messages .message-item'));

	            // Sort by seq (chronological order)
	            messages.sort(
	                (a, b) => getMessageSeq(a.id.replace('msg-', '')) - getMessageSeq(b.id.replace('msg-', ''))
	            );

	            // Build fragment
	            const fragment = document.createDocumentFragment();
	            messages.forEach(el => {
	                // Remove depth attribute
	                el.removeAttribute('data-depth');
	                fragment.appendChild(el);
	            });

	            // Replace children
	            messagesContainer.innerHTML = '';
	            messagesContainer.appendChild(fragment);

	            // Restore scroll position
	            scrollToMessage(anchorId);

	            // Re-apply collapse state
	            applyCollapsedState();
	        }

		        // Initial render on page load
			        document.addEventListener('DOMContentLoaded', () => {
			            renderMarkdown();
			            initGlobalControls();

			            // Scroll to bottom on topic detail pages.
			            if (document.getElementById('messages') && !isContextMode()) {
			                scrollToBottom(getMainScroll());
			            }

			            initTopicDetailPage();
			        });

        document.addEventListener('keydown', (e) => {
            const key = e.key.toLowerCase();
            if (key === 'k' && (e.metaKey || e.ctrlKey)) {
                e.preventDefault();
                focusTopicSearch();
            }
            if (key === 'escape') {
                closeTopicsDrawer();
            }
        });

        // Check if user is near the bottom of the scroll container
	        function isNearBottom(container, threshold = 100) {
	            if (!container) return false;
	            return container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
	        }

	        function isContextMode() {
	            const el = document.getElementById('context-mode');
	            return el && el.value === '1';
	        }

	        // Auto-scroll to bottom of messages container
	        function scrollToBottom(container) {
	            if (container) {
	                container.scrollTop = container.scrollHeight;
            }
        }

        let shouldAutoScrollMessages = false;

        document.addEventListener('htmx:beforeSwap', (e) => {
            if (e.detail?.target?.id === 'messages') {
                shouldAutoScrollMessages = isNearBottom(getMainScroll(), 150);
            }
        });

		        // Re-render after HTMX swaps in new content
		        document.addEventListener('htmx:afterSwap', (e) => {
		            const swappedTarget = e.detail?.target || e.target;
		            // For `hx-swap="outerHTML"` on `#app`, htmx may give us the old (detached) node.
		            // Always render against the current live `#app` so messages don't appear "collapsed"
		            // (empty markdown bodies) until the next poll.
		            const target = swappedTarget?.id === 'app' ? document.getElementById('app') : swappedTarget;
		            renderMarkdown(target);

		            // If we swapped the whole app (boosted navigation), re-init page behavior.
			            if (target?.id === 'app') {
			                initGlobalControls();
			                filterTopics(); // re-apply current query against the refreshed list
			                initTopicDetailPage();

			                // Scroll to bottom on topic detail pages.
			                if (document.getElementById('messages') && !isContextMode()) {
			                    scrollToBottom(getMainScroll());
			                }
			            }

	            // Thread UI + threaded view ordering when new messages are appended/prepended.
	            if (target?.id === 'messages' || target?.closest?.('#messages')) {
	                buildThreadTree();
	                if (threadedView) {
	                    renderThreadedView(false);
	                }
	            }

		            if (target?.id === 'messages' && shouldAutoScrollMessages && !isContextMode()) {
		                requestAnimationFrame(() => scrollToBottom(getMainScroll()));
		            }
		        });

	        // Expose actions for inline onclick handlers.
	        window.toggleSelectMode = toggleSelectMode;
	        window.toggleMessageSelection = toggleMessageSelection;
	        window.clearSelection = clearSelection;
	        window.deleteSelected = deleteSelected;
	        window.toggleThread = toggleThread;
	        window.collapseAllThreads = collapseAllThreads;
	        window.expandAllThreads = expandAllThreads;
	        window.toggleViewMode = toggleViewMode;
	    </script>
</head>
	<body class="text-xs antialiased">
		    <div
		        id="app"
		        class="drawer lg:drawer-open h-screen bg-base-100"
		    >
	        <input id="topics-drawer" type="checkbox" class="drawer-toggle" hx-preserve />

        <div class="drawer-content flex flex-col h-screen">
            <!-- Header -->
		            <div
		                class="flex items-center justify-between px-3 py-1.5 bg-base-200 border-b border-base-300"
		                hx-boost="true"
		                hx-target="#app"
		                hx-select="#app"
		                hx-swap="outerHTML transition:true"
		            >
                <div class="flex items-center gap-2 min-w-0">
                    <label
                        for="topics-drawer"
                        class="btn btn-xs btn-ghost lg:hidden"
                        title="Toggle topics sidebar"
                    >
                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                        </svg>
                    </label>
                    <a href="/" class="font-semibold text-sm hover:opacity-80 lg:hidden">AGENT BUS</a>
                    <div class="divider divider-horizontal m-0 h-4"></div>
                    {% block header_nav %}{% endblock %}
                </div>
                <div class="flex items-center gap-2">
                    {% block header_actions %}{% endblock %}
	                    <label class="swap swap-rotate btn btn-xs btn-ghost" title="Toggle theme">
	                        <input id="theme-toggle" type="checkbox" hx-preserve />
                        <!-- sun icon -->
                        <svg class="swap-off w-4 h-4 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                            <path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/>
                        </svg>
                        <!-- moon icon -->
                        <svg class="swap-on w-4 h-4 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                            <path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"/>
                        </svg>
                    </label>
                </div>
            </div>

            <!-- Main content -->
            <div id="main-scroll" class="flex-1 overflow-auto">
                {% block content %}{% endblock %}
            </div>

            <!-- Status bar -->
            <div class="flex items-center justify-between px-3 py-1 bg-base-300 border-t border-base-content/10 text-xs">
                <div class="flex items-center gap-4">
                    {% block status_left %}{% endblock %}
                </div>
                <div class="opacity-60">agent-bus v0.1.0</div>
            </div>
        </div>

	        <div
	            class="drawer-side"
	            hx-boost="true"
	            hx-target="#app"
	            hx-select="#app"
	            hx-swap="outerHTML transition:true"
	        >
            <label for="topics-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
            <aside class="bg-base-200 min-h-full w-80 border-r border-base-content/10 p-3">
                <div class="flex items-center justify-between px-1">
                    <a href="/" class="font-semibold text-sm hover:opacity-80">AGENT BUS</a>
                    {% if topics is defined %}
                    <span class="text-xs opacity-60">{{ topics|length }} topic{% if topics|length != 1 %}s{% endif %}</span>
                    {% endif %}
                </div>

                <div class="mt-3">
                    <label class="input input-sm flex items-center gap-2">
                        <svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                            <path d="M10 2a8 8 0 105.293 14.293l4.707 4.707 1.414-1.414-4.707-4.707A8 8 0 0010 2zm0 2a6 6 0 110 12 6 6 0 010-12z"/>
                        </svg>
	                        <input id="topic-search" type="search" class="grow" placeholder="Search topics" autocomplete="off" hx-preserve />
                        <kbd class="kbd kbd-xs">⌘</kbd>
                        <kbd class="kbd kbd-xs">K</kbd>
                    </label>
                </div>

                <div class="mt-3 space-y-1">
                    {% if topics is defined and topics %}
                        {% for t in topics %}
                        <div
                            class="topic-row group flex items-center justify-between rounded px-2 py-1 hover:bg-base-300/40"
                            data-topic-row
                            data-topic-hay="{{ t.name }} {{ t.topic_id }}"
                        >
                            <a
                                href="/topics/{{ t.topic_id }}"
                                onclick="closeTopicsDrawer()"
                                class="flex items-center gap-2 min-w-0 flex-1 {% if active_topic_id is defined and t.topic_id == active_topic_id %}font-semibold opacity-100{% else %}opacity-70{% endif %}"
                            >
                                <svg class="w-4 h-4 opacity-60 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    {% if t.status == 'open' %}
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
                                    {% else %}
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z" />
                                    {% endif %}
                                </svg>
                                <span class="truncate">{{ t.name }}</span>
                            </a>
                            <div class="flex items-center gap-1 flex-shrink-0">
                                <span class="text-xs opacity-50 w-6 text-right">{{ t.counts.messages }}</span>
                                <button
                                    class="btn btn-xs btn-ghost opacity-0 group-hover:opacity-60 hover:opacity-100 hover:text-error"
                                    hx-delete="/topics/{{ t.topic_id }}"
                                    hx-confirm="Delete topic '{{ t.name }}'? This will delete all messages and cannot be undone."
                                    hx-target="closest .topic-row"
                                    hx-swap="outerHTML"
                                    title="Delete topic"
                                >
                                    <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                                    </svg>
                                </button>
                            </div>
                        </div>
                        {% endfor %}
                    {% else %}
                        <div class="opacity-60 text-xs px-2 py-2">No topics yet.</div>
                    {% endif %}
                </div>
            </aside>
        </div>
    </div>
</body>
</html>
