{% extends "base.html" %}

{% block title %}Chatroom{% endblock %}

{% block content %}

    <div id="chat-container">
        <!-- Search form -->
        <div id="search-form">
            <form action="/search" method="get">
                <input type="text" id="search-keywords" name="keywords" placeholder="Search for keywords..." value="{{ keywords }}">
            </form>
        </div>

        <!-- Chat area where messages will be displayed -->
        <div id="chat"></div>

        <!-- Form for sending messages -->
        <form id="message-form">
            <textarea id="message" placeholder="Type your message..."></textarea>
            <button type="button" id="send-button" onclick="sendMessage()">Send</button>
        </form>
    </div>

    <div class="utility-belt">
        <!-- Room Actions -->
        {% if current_room %}
        <div class="room-actions-section" style="margin-bottom: 15px;">
            <h3 style="margin-top: 0; margin-bottom: 10px;">Room Actions</h3>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <button class="room-action-btn" onclick="forkRoom({{ current_room.id }})" style="width: 100%;">🍴 Fork</button>
                {% if user and current_room.owner_id == user.id %}
                <button class="room-action-btn delete-btn" onclick="deleteRoom({{ current_room.id }})" style="width: 100%;">🗑️ Delete</button>
                {% endif %}
            </div>
        </div>
        {% endif %}

        <div class="download-links">
            History
            <a href="/download_chat_history?room_name={{ room_name }}" download="{{ room_name }}.json">JSON</a> or
            <a href="/download_chat_history_md?room_name={{ room_name }}" download="{{ room_name }}.md">Markdown</a>
        </div>
        <br>
        <div>
            <label for="model-select">Model</label>
            <select id="model-select">
                <option value="None">None</option>
            </select>
        </div>
        <div>
            <label for="voice-select">Voice</label>
            <select id="voice-select">
                <!-- Voices will be populated from API -->
            </select>
        </div>
        <div>
            <button id="auto-play-tts-btn" onclick="toggleAutoPlayTTS()" style="width: 100%; margin-top: 10px; background-color: #f44336; color: white; border: none; padding: 8px; border-radius: 4px; cursor: pointer;">
                Auto-Play TTS: OFF
            </button>
        </div>

       <div id="activity-controls">
           <h3>Activities</h3>
           <div id="current-activity-info" style="display: none;">
               <p>Current Activity: <span id="current-activity-name"></span></p>
               <button id="cancel-activity-btn" onclick="cancelActivity()">Cancel Activity</button>
           </div>
           <div id="activity-list-section">
               <div style="display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 5px; margin-bottom: 5px;">
                   <select id="activity-select">
                       <option value="">-- Select an Activity --</option>
                   </select>
                   <button id="refresh-activities-btn" onclick="refreshActivityList()">🔄</button>
               </div>
               <button id="load-activity-btn" onclick="loadSelectedActivity()" style="margin-top: 5px;">Load Activity</button>
           </div>
       </div>

       <div id="user-lists">
           <div id="active-users-list">
               <h3>Active Users</h3>
               <ul id="active-users">
                   <!-- Active users will be dynamically populated here -->
               </ul>
           </div>
           <div id="inactive-users-list">
               <h3>Inactive Users</h3>
               <ul id="inactive-users">
                   <!-- Inactive users will be dynamically populated here -->
               </ul>
           </div>
       </div>

    </div>

<script>
// Constants
const API_KEY = "dummy-api-key";
const TTS_API_URL = "https://speech.ai.unturf.com/v1/audio/speech";
const VOICES_API_URL = "https://speech.ai.unturf.com/v1/voices";
// Code execution API (proxied through backend to keep API key secure)
const room_name = "{{ room_name }}";

// TTS Streaming: Detect best audio format for this browser
// Chrome/Edge: mp3 works, MediaSource supports audio/mpeg
// Firefox: mp3 often broken on Linux; use webm+opus
function detectAudioFormat() {
    const isFirefox = navigator.userAgent.includes('Firefox');
    if (isFirefox) {
        console.log("Firefox detected, using webm+opus format for TTS");
        return { format: 'webm', mime: 'audio/webm', mseMime: 'audio/webm;codecs=opus' };
    }
    // Chromium-based browsers: mp3 works, MSE supports audio/mpeg
    return { format: 'mp3', mime: 'audio/mpeg', mseMime: 'audio/mpeg' };
}
const AUDIO_FORMAT = detectAudioFormat();

// Get username from server (authenticated user's display name or None)
let username = {% if username %}"{{ username }}"{% else %}null{% endif %};

// If not authenticated, prompt for username
if (!username) {
    username = prompt("Enter your username:", "guest") || "guest";
}

// Configuration for DOMPurify to specify which tags and attributes are allowed
const dompurify_config = {
  ADD_TAGS: ["iframe", "img", "video"],
  FORBID_TAGS: ["form"], 
  ALLOWED_ATTR: [
    "src", "width", "height", "frameborder", "allowfullscreen", 
    "alt", "class", "title", "style", "controls",
  ]
};

// keeping track of scrolling to prevent autoscrolling.
let userHasScrolledUp = false;
let currentAudio = null; // To keep track of the currently playing audio
let currentQueuedAudio = null; // To keep track of currently playing queued TTS audio
let audioCache = {}; // Cache to store audio blobs

// Flag to prevent mutual updates on desktop/mobile
let isSyncingDropdowns = false;

// Auto-play TTS state
let autoPlayTTS = localStorage.getItem('autoPlayTTS') === 'true' || false;
let ttsQueue = [];
let isPlayingTTS = false;

// Vision model state for auto alt-text
let visionAvailable = false;
let visionModel = null;
const imageDescriptionCache = new Map();  // Cache descriptions by image src hash
const imageBase64Cache = new Map();  // Cache fetched external images as base64

// CORS proxy for ethical external image fetching (respects robots.txt)
const CORS_PROXY_URL = 'https://cors-proxy.uncloseai.com/api/fetch';

// Check vision availability on load
async function initVisionCapability() {
    try {
        const response = await fetch('/vision');
        const data = await response.json();
        visionAvailable = data.available;
        visionModel = data.default;
        if (visionAvailable) {
            console.log(`Vision available: ${visionModel}`);
            setupImageHoverDescriptions();
        }
    } catch (e) {
        console.warn('Vision check failed:', e);
    }
}

// Generate a simple hash for caching
function hashString(str) {
    let hash = 0;
    for (let i = 0; i < Math.min(str.length, 1000); i++) {
        hash = ((hash << 5) - hash) + str.charCodeAt(i);
        hash |= 0;
    }
    return hash.toString();
}

// Check if URL is an external image URL
function isExternalImageUrl(src) {
    if (!src) return false;
    if (src.startsWith('data:')) return false;
    try {
        const url = new URL(src);
        return url.protocol === 'http:' || url.protocol === 'https:';
    } catch {
        return false;
    }
}

// Fetch external image via CORS proxy and convert to base64
async function fetchImageAsBase64(imageUrl) {
    const cacheKey = hashString(imageUrl);
    if (imageBase64Cache.has(cacheKey)) {
        return imageBase64Cache.get(cacheKey);
    }

    try {
        // Use CORS proxy for ethical fetching (handles robots.txt server-side)
        const proxyUrl = `${CORS_PROXY_URL}?uri_target=${encodeURIComponent(imageUrl)}`;
        const response = await fetch(proxyUrl);

        if (!response.ok) {
            if (response.status === 403) {
                console.warn(`Image blocked by robots.txt: ${imageUrl}`);
                return null;
            }
            throw new Error(`HTTP ${response.status}`);
        }

        const blob = await response.blob();

        // Verify it's actually an image
        if (!blob.type.startsWith('image/')) {
            console.warn(`Not an image: ${imageUrl} (${blob.type})`);
            return null;
        }

        // Convert to base64
        return new Promise((resolve) => {
            const reader = new FileReader();
            reader.onloadend = () => {
                const base64 = reader.result;
                imageBase64Cache.set(cacheKey, base64);
                resolve(base64);
            };
            reader.onerror = () => resolve(null);
            reader.readAsDataURL(blob);
        });
    } catch (e) {
        console.warn(`Failed to fetch image: ${imageUrl}`, e);
        return null;
    }
}

// Get base64 image data (handles both base64 and external URLs)
async function getImageBase64(imgSrc) {
    if (imgSrc.startsWith('data:image')) {
        return imgSrc;
    }
    if (isExternalImageUrl(imgSrc)) {
        return await fetchImageAsBase64(imgSrc);
    }
    return null;
}

// Fetch description for an image
async function getImageDescription(imgSrc) {
    const cacheKey = hashString(imgSrc);
    if (imageDescriptionCache.has(cacheKey)) {
        return imageDescriptionCache.get(cacheKey);
    }

    // Get base64 version of image (fetch if external)
    const base64Image = await getImageBase64(imgSrc);
    if (!base64Image) {
        return null;
    }

    try {
        const response = await fetch('/vision/describe', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ image: base64Image })
        });
        const data = await response.json();
        if (data.description) {
            imageDescriptionCache.set(cacheKey, data.description);
            return data.description;
        }
    } catch (e) {
        console.warn('Failed to get image description:', e);
    }
    return null;
}

// Setup hover handlers for images in chat
function setupImageHoverDescriptions() {
    const messagesContainer = document.getElementById('messages');
    if (!messagesContainer) return;

    // Use mouseover (bubbles) for event delegation
    messagesContainer.addEventListener('mouseover', async (e) => {
        if (!visionAvailable) return;

        // Check if target is an image
        const img = e.target;
        if (img.tagName !== 'IMG') return;
        if (!img.src) return;

        // Handle both base64 and external URLs
        const isBase64 = img.src.startsWith('data:image');
        const isExternal = isExternalImageUrl(img.src);
        if (!isBase64 && !isExternal) return;

        if (img.dataset.visionProcessing || img.dataset.visionDone) return;

        // Mark as processing to avoid duplicate requests
        img.dataset.visionProcessing = 'true';
        img.style.cursor = 'wait';
        img.title = isExternal ? 'Fetching image...' : 'Generating description...';

        const description = await getImageDescription(img.src);
        if (description) {
            img.title = description;
            img.alt = description;
        } else {
            img.title = '';
        }

        img.style.cursor = '';
        delete img.dataset.visionProcessing;
        img.dataset.visionDone = 'true';
    });
}

// Function to sanitize the username
function sanitizeUsername(username) {
    // Split the username on commas and take the first part.
    // The backend denormalizes the user list in the room table via csv.
    return username.split(',')[0].trim();
}

// Function to copy message content to clipboard
function copyMessageContent(content) {
    navigator.clipboard.writeText(content).then(() => {
        // Optional: show a temporary success message
        const button = event.currentTarget;
        const originalText = button.textContent;
        button.textContent = 'Copied!';
        setTimeout(() => {
            button.textContent = originalText;
        }, 2000);
    }).catch(err => {
        console.error('Error copying text: ', err);
    });
}

// Function to sync dropdowns and save to localStorage
function syncDropdownsAndQueryString() {
    const modelSelectDesktop = document.getElementById("model-select");
    const voiceSelectDesktop = document.getElementById("voice-select");
    const modelSelectMobile = document.getElementById("model-select-mobile");
    const voiceSelectMobile = document.getElementById("voice-select-mobile");

    // Get current values
    const currentModel = modelSelectDesktop.value;
    const currentVoice = voiceSelectDesktop.value;

    // Sync dropdowns
    modelSelectDesktop.value = currentModel;
    voiceSelectDesktop.value = currentVoice;
    if (modelSelectMobile) modelSelectMobile.value = currentModel;
    if (voiceSelectMobile) voiceSelectMobile.value = currentVoice;

    // Save to localStorage for persistence
    localStorage.setItem('selectedModel', currentModel);
    localStorage.setItem('selectedVoice', currentVoice);
}

document.addEventListener('DOMContentLoaded', (event) => {
    const chatContainer = document.getElementById("chat");
    const modelSelectDesktop = document.getElementById("model-select");
    const voiceSelectDesktop = document.getElementById("voice-select");
    const modelSelectMobile = document.getElementById("model-select-mobile");
    const voiceSelectMobile = document.getElementById("voice-select-mobile");
    
    // Initialize auto-play TTS button state from localStorage
    updateAutoPlayTTSDisplay();

    // Check for vision model availability (enables image hover descriptions)
    initVisionCapability();

    // Function to populate the model dropdown
    function populateModelDropdown(models) {
        // Clear options starting from index 1 (preserve "None" at index 0)
        while (modelSelectDesktop.options.length > 1) {
            modelSelectDesktop.remove(1);
        }
        while (modelSelectMobile.options.length > 1) {
            modelSelectMobile.remove(1);
        }
        // Append new model options to both dropdowns
        models.forEach(modelId => {
            const option = document.createElement('option');
            option.value = modelId;
            option.textContent = modelId;
            modelSelectDesktop.appendChild(option);

            const optionMobile = document.createElement('option');
            optionMobile.value = modelId;
            optionMobile.textContent = modelId;
            modelSelectMobile.appendChild(optionMobile);
        });
        // Restore from localStorage (check if value exists in options)
        const storedModel = localStorage.getItem('selectedModel') || "None";
        const validOptions = Array.from(modelSelectDesktop.options).map(o => o.value);
        const modelToSelect = validOptions.includes(storedModel) ? storedModel : "None";
        modelSelectDesktop.value = modelToSelect;
        modelSelectMobile.value = modelToSelect;
        console.log(`Model restored from localStorage: ${modelToSelect}`);
    }

    // Function to populate the voice dropdown
    function populateVoiceDropdown(voicesData) {
        // Clear existing options
        voiceSelectDesktop.innerHTML = '';
        if (voiceSelectMobile) voiceSelectMobile.innerHTML = '';

        // Group voices by model
        const voicesByModel = {};
        voicesData.data.forEach(modelData => {
            const modelId = modelData.id;
            voicesByModel[modelId] = modelData.voices || [];
        });

        // Create optgroups for each model
        Object.entries(voicesByModel).forEach(([modelId, voices]) => {
            if (voices.length > 0) {
                const optgroup = document.createElement('optgroup');
                optgroup.label = modelId;

                voices.forEach(voice => {
                    const option = document.createElement('option');
                    option.value = `${modelId}:${voice}`;
                    option.textContent = `${modelId} - ${voice}`;
                    optgroup.appendChild(option);
                });

                voiceSelectDesktop.appendChild(optgroup);
                if (voiceSelectMobile) {
                    voiceSelectMobile.appendChild(optgroup.cloneNode(true));
                }
            }
        });

        // Set initial value from localStorage or first available option (not URL)
        const initialVoice = localStorage.getItem('selectedVoice') || voiceSelectDesktop.options[0]?.value;
        if (initialVoice) {
            voiceSelectDesktop.value = initialVoice;
            if (voiceSelectMobile) voiceSelectMobile.value = initialVoice;
            console.log(`Voice restored from localStorage: ${initialVoice}`);
        }
    }

    // Memoization with localStorage (1-minute cache)
    const cacheKey = 'modelList';
    const cacheExpirationKey = 'modelListExpiration';
    const cacheDuration = 60 * 1000; // 1 minute in milliseconds

    const cachedData = localStorage.getItem(cacheKey);
    const cachedExpiration = localStorage.getItem(cacheExpirationKey);

    if (cachedData && cachedExpiration && Date.now() < parseInt(cachedExpiration)) {
        // Use cached data if it exists and hasn't expired
        const models = JSON.parse(cachedData);
        populateModelDropdown(models);
    } else {
        // Fetch from backend and update cache
        fetch('/models')
            .then(response => response.json())
            .then(data => {
                const models = data.models;
                populateModelDropdown(models);
                // Store in localStorage with expiration
                localStorage.setItem(cacheKey, JSON.stringify(models));
                localStorage.setItem(cacheExpirationKey, Date.now() + cacheDuration);
            })
            .catch(error => console.error("Error fetching models:", error));
    }

    // Fetch and populate voices with caching
    const voicesCacheKey = 'voicesList';
    const voicesCacheExpirationKey = 'voicesListExpiration';

    const cachedVoices = localStorage.getItem(voicesCacheKey);
    const cachedVoicesExpiration = localStorage.getItem(voicesCacheExpirationKey);

    if (cachedVoices && cachedVoicesExpiration && Date.now() < parseInt(cachedVoicesExpiration)) {
        // Use cached voices data
        const voicesData = JSON.parse(cachedVoices);
        populateVoiceDropdown(voicesData);
    } else {
        // Fetch voices from API
        fetch(VOICES_API_URL, {
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            }
        })
            .then(response => response.json())
            .then(voicesData => {
                populateVoiceDropdown(voicesData);
                // Store in localStorage with expiration
                localStorage.setItem(voicesCacheKey, JSON.stringify(voicesData));
                localStorage.setItem(voicesCacheExpirationKey, Date.now() + cacheDuration);
            })
            .catch(error => {
                console.error("Error fetching voices:", error);
                // Leave dropdown empty if API fails
                voiceSelectDesktop.innerHTML = '';
                if (voiceSelectMobile) voiceSelectMobile.innerHTML = '';
            });
    }

    chatContainer.addEventListener('scroll', () => {
        const distanceFromBottom = chatContainer.scrollHeight - chatContainer.scrollTop - chatContainer.clientHeight;
        userHasScrolledUp = distanceFromBottom > 5;
    });

    // NOTE: Model and voice restoration happens in populateModelDropdown() and
    // populateVoiceDropdown() AFTER the async fetch completes. Don't set values
    // here or call syncDropdownsAndQueryString() - that would overwrite localStorage
    // with empty values before the dropdowns are populated.

    // Add event listeners for desktop dropdowns
    modelSelectDesktop.addEventListener("change", () => {
        if (isSyncingDropdowns) return;
        isSyncingDropdowns = true;
        modelSelectMobile.value = modelSelectDesktop.value;
        syncDropdownsAndQueryString();
        isSyncingDropdowns = false;
    });

    voiceSelectDesktop.addEventListener("change", () => {
        if (isSyncingDropdowns) return;
        isSyncingDropdowns = true;
        voiceSelectMobile.value = voiceSelectDesktop.value;
        syncDropdownsAndQueryString();
        isSyncingDropdowns = false;
    });

    // Add event listeners for mobile dropdowns
    modelSelectMobile.addEventListener("change", () => {
        if (isSyncingDropdowns) return;
        isSyncingDropdowns = true;
        modelSelectDesktop.value = modelSelectMobile.value;
        syncDropdownsAndQueryString();
        isSyncingDropdowns = false;
    });

    voiceSelectMobile.addEventListener("change", () => {
        if (isSyncingDropdowns) return;
        isSyncingDropdowns = true;
        voiceSelectDesktop.value = voiceSelectMobile.value;
        syncDropdownsAndQueryString();
        isSyncingDropdowns = false;
    });
});

// Socket event when the user connects
socket.on("connect", () => {
    // Sanitize the username before joining
    const sanitizedUsername = sanitizeUsername(username);
    socket.emit("join", {"username": sanitizedUsername, "room_name": room_name});
});

// Function to update the active and inactive user lists in the DOM
function updateUserLists(activeUsers, inactiveUsers) {
    const activeUserListElement = document.getElementById("active-users");
    const inactiveUserListElement = document.getElementById("inactive-users");
    const activeUserListElementMobile = document.getElementById("active-users-mobile");
    const inactiveUserListElementMobile = document.getElementById("inactive-users-mobile");

    activeUserListElement.innerHTML = ''; // Clear the current list
    inactiveUserListElement.innerHTML = ''; // Clear the current list
    activeUserListElementMobile.innerHTML = ''; // Clear the current list (mobile)
    inactiveUserListElementMobile.innerHTML = ''; // Clear the current list (mobile)

    // Populate the list with active users (desktop)
    activeUsers.forEach(user => {
        const userItem = document.createElement("li");
        // If it's the logged-in user, make it clickable to profile
        if (user === username) {
            const userLink = document.createElement("a");
            userLink.href = "/profile";
            userLink.textContent = user;
            userLink.style.color = "var(--text-primary)";
            userLink.style.textDecoration = "none";
            userItem.appendChild(userLink);
        } else {
            userItem.textContent = user;
        }
        activeUserListElement.appendChild(userItem);
    });

    // Populate the list with inactive users (desktop)
    inactiveUsers.forEach(user => {
        const userItem = document.createElement("li");
        // If it's the logged-in user, make it clickable to profile
        if (user === username) {
            const userLink = document.createElement("a");
            userLink.href = "/profile";
            userLink.textContent = user;
            userLink.style.color = "var(--text-secondary)";
            userLink.style.textDecoration = "none";
            userItem.appendChild(userLink);
        } else {
            userItem.textContent = user;
        }
        inactiveUserListElement.appendChild(userItem);
    });

    // Populate the list with active users (mobile)
    activeUsers.forEach(user => {
        const userItemMobile = document.createElement("li");
        // If it's the logged-in user, make it clickable to profile
        if (user === username) {
            const userLink = document.createElement("a");
            userLink.href = "/profile";
            userLink.textContent = user;
            userLink.style.color = "var(--text-primary)";
            userLink.style.textDecoration = "none";
            userItemMobile.appendChild(userLink);
        } else {
            userItemMobile.textContent = user;
        }
        activeUserListElementMobile.appendChild(userItemMobile);
    });

    // Populate the list with inactive users (mobile)
    inactiveUsers.forEach(user => {
        const userItemMobile = document.createElement("li");
        // If it's the logged-in user, make it clickable to profile
        if (user === username) {
            const userLink = document.createElement("a");
            userLink.href = "/profile";
            userLink.textContent = user;
            userLink.style.color = "var(--text-secondary)";
            userLink.style.textDecoration = "none";
            userItemMobile.appendChild(userLink);
        } else {
            userItemMobile.textContent = user;
        }
        inactiveUserListElementMobile.appendChild(userItemMobile);
    });
}

// Update user lists whenever the event is received
socket.on("active_users", (data) => {
    updateUserLists(data.active_users, data.inactive_users);
});

// Function to handle sending the message
function sendMessage() {
    const messageTextarea = document.getElementById("message");
    const message = messageTextarea.value;
    const model = document.getElementById("model-select").value;
    let messageToSend = message.trim();

    if (messageToSend !== "") {  // Ensure we're not sending empty messages
        socket.emit("chat_message", {
            "username": username,
            "message": messageToSend,
            "model": model,  // Pass model as a separate attribute
            "room_name": room_name
        });
        messageTextarea.value = "";
        // Reset textarea height after sending
        messageTextarea.style.height = 'auto';
    }
}

// Function to handle deleting a message
function deleteMessage(messageId, room_name) {
    socket.emit("delete_message", {"message_id": messageId, "room_name": room_name});
}

// Event listener for form submission to send a message
document.getElementById("message-form").addEventListener("submit", (e) => {
    e.preventDefault();
    sendMessage();
});

// Event listener for the Enter key press in the textarea to send a message
document.getElementById("message").addEventListener("keydown", function(e) {
    if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        sendMessage();
    }
});

// Auto-grow textarea as user types
document.getElementById("message").addEventListener("input", function() {
    // Reset height to auto to get the correct scrollHeight
    this.style.height = 'auto';
    // Set height to scrollHeight to fit content
    this.style.height = this.scrollHeight + 'px';
});

// Socket event for updating the room title
socket.on("update_room_title", (data) => {
    document.title = data.title; // Update the window's title
});

// Socket event to update the room title in the sidebar or add new rooms.
socket.on('update_room_list', function(updatedRoom) {
    // Find the room list item by its data-room-id attribute
    const roomListItem = document.querySelector(`#rooms-list li[data-room-id="${updatedRoom.id}"]`);

    if (roomListItem) {
        // Room exists - update it
        // Extract user count from existing content (safer than regex on innerHTML)
        const userCountText = roomListItem.textContent.match(/(\d+)\s*users/);
        const userCount = userCountText ? userCountText[1] : null;
        const ownerBadge = roomListItem.querySelector('.owner-badge');

        // Clear and rebuild content safely using DOM methods
        roomListItem.innerHTML = '';

        // Add room name
        const nameElement = document.createElement('b');
        nameElement.textContent = updatedRoom.name;
        roomListItem.appendChild(nameElement);

        // Add room title if present
        if (updatedRoom.title) {
            roomListItem.appendChild(document.createElement('br'));
            const titleText = document.createTextNode(updatedRoom.title);
            roomListItem.appendChild(titleText);
        }

        // Add user count if it existed
        if (userCount) {
            roomListItem.appendChild(document.createElement('br'));
            const userText = document.createTextNode(` ${userCount} users`);
            roomListItem.appendChild(userText);
        }

        // Re-add owner badge if it existed
        if (ownerBadge) {
            roomListItem.appendChild(ownerBadge);
        }
    } else if (updatedRoom.is_new) {
        // New room - add it to the appropriate list
        const targetList = updatedRoom.is_private
            ? document.querySelector('#private-rooms-section .rooms-list')
            : document.querySelector('#public-rooms-section .rooms-list');

        if (targetList) {
            // Create new room list item using safe DOM methods
            const newLink = document.createElement('a');
            newLink.href = `/chat/${encodeURIComponent(updatedRoom.name)}`;

            const newLi = document.createElement('li');
            newLi.setAttribute('data-room-id', updatedRoom.id);
            if (updatedRoom.is_private) {
                newLi.classList.add('private-room');
            }

            // Add room name safely
            const nameElement = document.createElement('b');
            nameElement.textContent = updatedRoom.name;
            newLi.appendChild(nameElement);

            // Add room title if present
            if (updatedRoom.title) {
                newLi.appendChild(document.createElement('br'));
                const titleText = document.createTextNode(updatedRoom.title);
                newLi.appendChild(titleText);
            }

            // Add user count
            newLi.appendChild(document.createElement('br'));
            const userText = document.createTextNode(' 0 users');
            newLi.appendChild(userText);

            newLink.appendChild(newLi);
            targetList.appendChild(newLink);
        }
    }
});

// Helper function to enable and wire download button
function enableDownloadButton(messageId, playButton, audioUrl, voice) {
    const messageWrapper = playButton.closest('.message-wrapper');
    if (!messageWrapper) return;

    const downloadButton = messageWrapper.querySelector('.tts-download-button');
    // Use == instead of === because dataset values are strings, messageId might be number
    if (downloadButton && downloadButton.dataset.messageId == messageId) {
        downloadButton.style.display = "inline-block";
        downloadButton.onclick = () => {
            const link = document.createElement('a');
            link.href = audioUrl;
            link.download = `tts-${messageId}-${voice}.${AUDIO_FORMAT.format}`;
            link.click();
        };
    }
}

// Streaming TTS function - plays audio as chunks arrive using MediaSource API
// Returns a promise that resolves with { audio, blob, blobUrl, streamed }
async function fetchTTSStreaming(cleanText, model, voice) {
    const mseMime = AUDIO_FORMAT.mseMime;
    const canStream = mseMime && window.MediaSource && MediaSource.isTypeSupported(mseMime);
    const requestFormat = AUDIO_FORMAT.format;

    console.log(`TTS streaming: canStream=${canStream}, format=${requestFormat}, mseMime=${mseMime}`);

    const response = await fetch(TTS_API_URL, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${API_KEY}`
        },
        body: JSON.stringify({
            model: model,
            voice: voice,
            input: cleanText,
            response_format: requestFormat
        })
    });

    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }

    // If MSE isn't available, fall back to full buffered download
    if (!canStream) {
        console.warn(`No streaming support for ${requestFormat}, using full buffer`);
        const audioBlob = await response.blob();
        const audioUrl = URL.createObjectURL(audioBlob);
        const audio = new Audio(audioUrl);
        audio.playbackRate = 0.9;
        return { audio, blob: audioBlob, blobUrl: audioUrl, streamed: false };
    }

    // Use MediaSource API for true streaming
    const mediaSource = new MediaSource();
    const audioUrl = URL.createObjectURL(mediaSource);
    const audio = new Audio(audioUrl);
    audio.playbackRate = 0.9;
    console.log(`TTS streaming: MediaSource created, readyState=${mediaSource.readyState}`);

    // Collect all chunks for download later
    const chunks = [];

    // Create a promise that resolves when streaming is complete
    const streamingComplete = new Promise((resolve, reject) => {
        mediaSource.addEventListener('sourceopen', async () => {
            console.log("TTS streaming: sourceopen fired");
            let sourceBuffer;
            try {
                sourceBuffer = mediaSource.addSourceBuffer(mseMime);
                sourceBuffer.mode = 'sequence';
            } catch (e) {
                console.error("Failed to create SourceBuffer:", e);
                reject(e);
                return;
            }

            const reader = response.body.getReader();
            let totalBytes = 0;

            // Queue for appending buffers (SourceBuffer can only append one at a time)
            const appendQueue = [];
            let appending = false;

            function processQueue() {
                if (appending || appendQueue.length === 0) return;
                appending = true;
                const chunk = appendQueue.shift();
                try {
                    sourceBuffer.appendBuffer(chunk);
                } catch (e) {
                    console.error("appendBuffer error:", e);
                    appending = false;
                }
            }

            sourceBuffer.addEventListener('updateend', () => {
                appending = false;
                processQueue();
            });

            try {
                while (true) {
                    const { done: readerDone, value } = await reader.read();
                    if (readerDone) break;

                    chunks.push(value);
                    totalBytes += value.byteLength;

                    // Queue the chunk for appending (use slice to avoid shared ArrayBuffer issues)
                    appendQueue.push(value.slice().buffer);
                    processQueue();

                    // Auto-play once we have some data (~1KB)
                    if (totalBytes > 1024 && audio.paused) {
                        console.log("TTS streaming: starting playback at", totalBytes, "bytes");
                        audio.play().catch(e => console.warn("Auto-play blocked:", e.message));
                    }
                }

                // Wait for all queued appends to finish
                await new Promise((res) => {
                    const check = () => {
                        if (!appending && appendQueue.length === 0) {
                            res();
                        } else {
                            setTimeout(check, 50);
                        }
                    };
                    check();
                });

                if (mediaSource.readyState === 'open') {
                    mediaSource.endOfStream();
                }

                console.log(`TTS streaming complete: ${totalBytes} bytes`);

                // Build blob for download/caching
                const audioBlob = new Blob(chunks, { type: mseMime });
                resolve({ blob: audioBlob });

            } catch (error) {
                console.error("Streaming read error:", error);
                if (mediaSource.readyState === 'open') {
                    mediaSource.endOfStream('network');
                }
                reject(error);
            }
        });

        mediaSource.addEventListener('error', (e) => {
            console.error("MediaSource error:", e);
            reject(new Error("MediaSource error"));
        });
    });

    // Return immediately with audio element - streaming happens in background
    return {
        audio,
        blobUrl: audioUrl,
        streamed: true,
        streamingComplete // Promise that resolves with { blob } when done
    };
}

// Function to read text using TTS (for manual button clicks) - now with streaming
async function speakText(text, playButton, messageId) {
    console.log("speakText called with:", {text: text, messageId, autoPlay: autoPlayTTS});
    const voiceSelectValue = document.getElementById("voice-select").value;

    // Parse model and voice from the dropdown value (format: "model:voice")
    const [model, voice] = voiceSelectValue.includes(':') ? voiceSelectValue.split(':') : ['tts-1', voiceSelectValue];
    const cacheKey = `${messageId}-${voiceSelectValue}`; // Unique cache key for each message and voice

    // Clean the text to include only alphanumeric characters, spaces, and key punctuation
    const cleanText = text.replace(/[^a-zA-Z0-9\s.,!?]/g, '');

    try {
        // Check if the audio is already cached
        if (audioCache[cacheKey]) {
            const cachedData = audioCache[cacheKey];
            // Create new audio from cached blob for replay
            const audioUrl = URL.createObjectURL(cachedData.blob);
            const audio = new Audio(audioUrl);
            audio.playbackRate = 0.9;
            enableDownloadButton(messageId, playButton, audioUrl, voice);
            toggleAudioPlayback(audio, playButton);
            return;
        }

        // Set button to streaming state
        playButton.textContent = "Streaming...";
        playButton.disabled = true;

        // Use streaming TTS
        const result = await fetchTTSStreaming(cleanText, model, voice);
        const audio = result.audio;

        // Enable button immediately and start playback
        playButton.disabled = false;
        toggleAudioPlayback(audio, playButton);

        // Handle streaming completion for caching and download button
        if (result.streamed && result.streamingComplete) {
            result.streamingComplete.then(({ blob }) => {
                // Cache the blob for replay
                audioCache[cacheKey] = { blob };
                // Create downloadable URL from blob
                const downloadUrl = URL.createObjectURL(blob);
                enableDownloadButton(messageId, playButton, downloadUrl, voice);
            }).catch(e => console.error("Streaming completion error:", e));
        } else {
            // Non-streamed fallback - cache immediately
            audioCache[cacheKey] = { blob: result.blob };
            enableDownloadButton(messageId, playButton, result.blobUrl, voice);
        }
    } catch (error) {
        console.error('Error in TTS:', error);
        playButton.textContent = "Play"; // Reset button text on error
        playButton.disabled = false;
    }
}

// Function to read text using TTS (for queued auto-play) - now with streaming
async function speakTextQueued(text, playButton, messageId) {
    return new Promise(async (resolve, reject) => {
        const voiceSelectValue = document.getElementById("voice-select").value;

        // Parse model and voice from the dropdown value (format: "model:voice")
        const [model, voice] = voiceSelectValue.includes(':') ? voiceSelectValue.split(':') : ['tts-1', voiceSelectValue];
        const cacheKey = `${messageId}-${voiceSelectValue}`;
        const cleanText = text.replace(/[^a-zA-Z0-9\s.,!?]/g, '');

        // Check if audio is cached
        if (audioCache[cacheKey]) {
            const cachedData = audioCache[cacheKey];
            // Create new audio from cached blob for replay
            const audioUrl = URL.createObjectURL(cachedData.blob);
            const audio = new Audio(audioUrl);
            audio.playbackRate = 0.9;
            enableDownloadButton(messageId, playButton, audioUrl, voice);

            currentQueuedAudio = audio;
            audio.onended = () => {
                console.log("TTS finished for:", messageId);
                currentQueuedAudio = null;
                resolve();
            };
            audio.onerror = () => {
                console.error("TTS audio error for:", messageId);
                currentQueuedAudio = null;
                reject(new Error("Audio playback failed"));
            };
            audio.play().catch(reject);
            return;
        }

        try {
            // Use streaming TTS for faster playback start
            const result = await fetchTTSStreaming(cleanText, model, voice);
            const audio = result.audio;

            currentQueuedAudio = audio;
            audio.onended = () => {
                console.log("TTS finished for:", messageId);
                currentQueuedAudio = null;
                resolve();
            };
            audio.onerror = () => {
                console.error("TTS audio error for:", messageId);
                currentQueuedAudio = null;
                reject(new Error("Audio playback failed"));
            };

            // Handle streaming completion for caching
            if (result.streamed && result.streamingComplete) {
                result.streamingComplete.then(({ blob }) => {
                    // Cache the blob for replay
                    audioCache[cacheKey] = { blob };
                    // Create downloadable URL from blob
                    const downloadUrl = URL.createObjectURL(blob);
                    enableDownloadButton(messageId, playButton, downloadUrl, voice);
                }).catch(e => console.error("Streaming completion error:", e));
            } else {
                // Non-streamed fallback - cache immediately
                audioCache[cacheKey] = { blob: result.blob };
                enableDownloadButton(messageId, playButton, result.blobUrl, voice);
            }

            // Audio should auto-play from streaming, but ensure it starts
            if (audio.paused) {
                audio.play().catch(reject);
            }
        } catch (error) {
            reject(error);
        }
    });
}

// Function to add TTS to queue
function queueTTS(text, playButton, messageId) {
    ttsQueue.push({ text, playButton, messageId });
    console.log("Added to TTS queue:", messageId, "Queue length:", ttsQueue.length);
    processNextTTS();
}

// Function to process the next TTS in queue
function processNextTTS() {
    if (isPlayingTTS || ttsQueue.length === 0) {
        return;
    }
    
    isPlayingTTS = true;
    const { text, playButton, messageId } = ttsQueue.shift();
    console.log("Processing TTS from queue:", messageId);
    
    // Use non-blocking async processing
    speakTextQueued(text, playButton, messageId)
        .then(() => {
            console.log("TTS completed successfully for:", messageId);
        })
        .catch((error) => {
            console.error("TTS error:", error);
        })
        .finally(() => {
            isPlayingTTS = false;
            // Schedule next item with minimal delay to prevent blocking
            setTimeout(processNextTTS, 10);
        });
}

// Function to update auto-play TTS button display
function updateAutoPlayTTSDisplay() {
    const autoPlayBtn = document.getElementById("auto-play-tts-btn");
    const autoPlayBtnMobile = document.getElementById("auto-play-tts-btn-mobile");
    
    if (autoPlayTTS) {
        autoPlayBtn.textContent = "Auto-Play TTS: ON";
        autoPlayBtn.style.backgroundColor = "#4CAF50";
        autoPlayBtnMobile.textContent = "Auto-Play TTS: ON";
        autoPlayBtnMobile.style.backgroundColor = "#4CAF50";
    } else {
        autoPlayBtn.textContent = "Auto-Play TTS: OFF";
        autoPlayBtn.style.backgroundColor = "#f44336";
        autoPlayBtnMobile.textContent = "Auto-Play TTS: OFF";
        autoPlayBtnMobile.style.backgroundColor = "#f44336";
        // Clear queue when turning off
        ttsQueue = [];
        isPlayingTTS = false;
    }
}

// Function to toggle auto-play TTS
function toggleAutoPlayTTS() {
    autoPlayTTS = !autoPlayTTS;
    console.log("Auto-play TTS toggled to:", autoPlayTTS);
    
    // Save to localStorage
    localStorage.setItem('autoPlayTTS', autoPlayTTS.toString());
    
    // If turning off, clear the queue and stop current audio
    if (!autoPlayTTS) {
        console.log("Clearing TTS queue, had", ttsQueue.length, "items");
        ttsQueue = [];
        isPlayingTTS = false;
        
        // Stop any currently playing audio
        if (currentAudio) {
            currentAudio.pause();
            currentAudio.currentTime = 0;
            if (currentAudio.playButton) {
                currentAudio.playButton.textContent = "Play";
            }
            currentAudio = null;
        }
        
        // Stop any currently playing queued TTS audio
        if (currentQueuedAudio) {
            currentQueuedAudio.pause();
            currentQueuedAudio.currentTime = 0;
            currentQueuedAudio = null;
        }
    }
    
    updateAutoPlayTTSDisplay();
}

// Function to toggle audio playback
function toggleAudioPlayback(audio, playButton) {
    if (currentAudio && currentAudio !== audio) {
        currentAudio.pause();
        currentAudio.currentTime = 0;
        currentAudio.playButton.textContent = "Play";
    }

    if (audio.paused) {
        audio.play();
        playButton.textContent = "Pause";
    } else {
        audio.pause();
        playButton.textContent = "Play";
    }

    currentAudio = audio;
    currentAudio.playButton = playButton;

    audio.onended = () => {
        playButton.textContent = "Play";
    };
}

// Socket event for receiving a new message
socket.on("chat_message", (data) => {
    const messageWrapper = document.createElement("div");
    messageWrapper.className = "message-wrapper";
    messageWrapper.id = "message-" + data.id;

    const newMessage = document.createElement("div");
    newMessage.className = "message-content";

    // Check if the username is present and prepend it to the message content
    // Link to /profile if it's the logged-in user, otherwise /profile/username
    let messageContent;
    if (data.username) {
        const profileLink = data.username === username ? '/profile' : `/profile/${data.username}`;
        messageContent = `**[${data.username}](${profileLink}):**\n\n${data.content}`;
    } else {
        messageContent = data.content;
    }

    // Check if the message starts with a base64 image tag
    if (data.content.startsWith('<img src="data:image/jpeg;base64')) {
        // Directly assign the message as innerHTML if it starts with a base64 image
        newMessage.innerHTML = data.content;
    } else {
        // Otherwise, sanitize and process the message with marked
        newMessage.innerHTML = DOMPurify.sanitize(marked.marked(messageContent), dompurify_config);
    }

    // Check if the message has an id which means we can delete it.
    if (data.id) {
        newMessage.dataset.rawMarkdown = data.content;

        // Create a container for the buttons
        const buttonContainer = document.createElement("div");
        buttonContainer.className = "button-container";

        // Create the delete button
        const deleteButton = document.createElement("button");
        deleteButton.innerHTML = "x";
        deleteButton.onclick = () => deleteMessage(data.id, room_name);
        buttonContainer.appendChild(deleteButton);

        // Create the edit button
        const editButton = document.createElement("button");
        editButton.textContent = "Edit";
        editButton.className = "edit-button";
        editButton.onclick = () => editMessage(data.id, newMessage, data.content);
        buttonContainer.appendChild(editButton);

        // Create the copy button
        const copyButton = document.createElement("button");
        copyButton.textContent = "Copy";
        copyButton.className = "copy-button";
        copyButton.onclick = () => copyMessageContent(data.content);
        buttonContainer.appendChild(copyButton);

        // Create the play button for TTS
        const playButton = document.createElement("button");
        playButton.textContent = "Play";
        playButton.onclick = () => speakText(data.content, playButton, data.id);
        buttonContainer.appendChild(playButton);

        // Create the download button for TTS audio (hidden initially)
        const downloadButton = document.createElement("button");
        downloadButton.textContent = "Download";
        downloadButton.className = "tts-download-button";
        downloadButton.style.display = "none";
        downloadButton.dataset.messageId = data.id;
        buttonContainer.appendChild(downloadButton);

        messageWrapper.appendChild(buttonContainer);
    }

    messageWrapper.appendChild(newMessage);

    document.getElementById("chat").appendChild(messageWrapper);

    // Apply syntax highlighting to code blocks within the message
    // Don't truncate new messages - only truncate on page load (previous_messages)
    newMessage.querySelectorAll("pre code").forEach((block) => {
        hljs.highlightElement(block);
        addLineNumbers(block);
        addCopyButtonToCodeBlock(block, false);
    });

    // Scroll to the bottom of the chat container to show the new message.
    if (data.id) {
        document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;

        // Check if this message should auto-execute (from auto-fix)
        if (window.pendingAutoExec) {
            const autoExecData = window.pendingAutoExec;
            window.pendingAutoExec = null; // Clear it so we don't re-execute

            // Find the code block that was just added
            const codeBlocks = newMessage.querySelectorAll("pre code");
            if (codeBlocks.length > 0) {
                // Get the first code block (should be the fixed code)
                const codeBlock = codeBlocks[0];

                // Find the Run button for this code block
                setTimeout(() => {
                    // The Run button is in a sibling container after the <pre> element
                    const preElement = codeBlock.parentNode;
                    const buttonContainer = preElement.nextSibling;
                    const runButton = buttonContainer?.querySelector('.play-button');

                    if (runButton) {
                        console.log(`Auto-executing fixed code (attempt ${autoExecData.attempt}/3)...`);

                        // Store the attempt count so executeCodeBlock can pick it up
                        // We'll use a data attribute on the code block itself
                        codeBlock.dataset.autoExecAttempt = autoExecData.attempt.toString();

                        // Trigger execution - it will create its own results container
                        runButton.click();
                    }
                }, 100); // Delay to ensure buttons are fully rendered
            }
        }

        // Auto-play TTS if enabled and message has content - AFTER buttons are created
        if (autoPlayTTS && data.content && data.content.trim() !== "") {
            setTimeout(() => {
                // Find the play button after buttons have been created
                const buttons = messageWrapper.querySelectorAll("button");
                const playButton = Array.from(buttons).find(btn => btn.textContent === "Play");
                console.log("Auto-play TTS: enabled=", autoPlayTTS, "content=", data.content, "playButton=", playButton);
                if (playButton) {
                    console.log("Queueing TTS for message:", data.id);
                    queueTTS(data.content, playButton, data.id);
                }
            }, 10); // Very short delay to let buttons be created
        }
    }
});

// Socket event for receiving previous messages
socket.on("previous_messages", (data) => {

    if (document.getElementById("message-" + data.id)) {
        // If it exists, skip appending it
        return;
    }

    const messageWrapper = document.createElement("div");
    messageWrapper.className = "message-wrapper";
    messageWrapper.id = "message-" + data.id;

    const newMessage = document.createElement("div");
    newMessage.className = "message-content";

    // Check if the message contains a base64 image
    if (data.content.startsWith('<img src="data:image/jpeg;base64')) {
        // Directly assign the message as innerHTML if it's a base64 image
        newMessage.innerHTML = data.content;
    } else {
        // Link to /profile if it's the logged-in user, otherwise /profile/username
        const profileLink = data.username === username ? '/profile' : `/profile/${data.username}`;
        const usernameLink = `**[${data.username}](${profileLink}):**`;
        // Otherwise, sanitize and process the message with marked
        newMessage.innerHTML = DOMPurify.sanitize(marked.marked(`${usernameLink}\n\n${data.content}`), dompurify_config);
    }

    newMessage.dataset.rawMarkdown = data.content;

    // Create a container for the buttons
    const buttonContainer = document.createElement("div");
    buttonContainer.className = "button-container";

    // Create the delete button
    const deleteButton = document.createElement("button");
    deleteButton.innerHTML = "x";
    deleteButton.onclick = () => deleteMessage(data.id, room_name);
    buttonContainer.appendChild(deleteButton);

    // Create the edit button
    const editButton = document.createElement("button");
    editButton.textContent = "Edit";
    editButton.className = "edit-button";
    editButton.onclick = () => editMessage(data.id, newMessage);
    buttonContainer.appendChild(editButton);

    // Create the copy button
    const copyButton = document.createElement("button");
    copyButton.textContent = "Copy";
    copyButton.className = "copy-button";
    copyButton.onclick = () => copyMessageContent(data.content);
    buttonContainer.appendChild(copyButton);

    // Create the play button for TTS
    const playButton = document.createElement("button");
    playButton.textContent = "Play";
    playButton.onclick = () => speakText(data.content, playButton, data.id);
    buttonContainer.appendChild(playButton);

    // Create the download button for TTS audio (hidden initially)
    const downloadButton = document.createElement("button");
    downloadButton.textContent = "Download";
    downloadButton.className = "tts-download-button";
    downloadButton.style.display = "none";
    downloadButton.dataset.messageId = data.id;
    buttonContainer.appendChild(downloadButton);

    messageWrapper.appendChild(buttonContainer);
    messageWrapper.appendChild(newMessage);

    document.getElementById("chat").appendChild(messageWrapper);

    // Apply syntax highlighting to code blocks within the message
    newMessage.querySelectorAll("pre code").forEach((block) => {
        const wasTruncated = truncateCodeBlock(block);
        hljs.highlightElement(block);
        addLineNumbers(block);
        addCopyButtonToCodeBlock(block, wasTruncated);
    });

    // Scroll to the bottom of the chat container
    document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;

});

// Socket event for deleting a processing message
socket.on("delete_processing_message", (msg_id) => {
    const tempMessages = document.querySelectorAll("#message-null");
    tempMessages.forEach((tempMessage) => {
        tempMessage.remove();
    });
    // Clear the message buffer and header for the corresponding message ID
    delete messageBuffers[msg_id];
    delete messageHeaders[msg_id];
});

// A dictionary to hold buffers for each message ID
const messageBuffers = {};
// A dictionary to track message headers (username/model) for each message ID
const messageHeaders = {};

// Socket event for receiving chunks of a message
socket.on("message_chunk", (data) => {
    const wrapperId = "message-" + data.id;
    let messageWrapper = document.getElementById(wrapperId);
    let targetMessageElement;

    // If the message wrapper doesn't exist, create it
    if (!messageWrapper) {
        messageWrapper = document.createElement("div");
        messageWrapper.className = "message-wrapper";
        messageWrapper.id = wrapperId;
        document.getElementById("chat").appendChild(messageWrapper);
    }

    // If the message-content div doesn't exist, create it
    if (!messageWrapper.querySelector(".message-content")) {
        // Create a message body wrapper to contain both header and content
        const messageBodyWrapper = document.createElement("div");
        messageBodyWrapper.className = "message-body";
        messageWrapper.appendChild(messageBodyWrapper);
        
        // Create header element for username/model
        const headerElement = document.createElement("div");
        headerElement.className = "message-header";
        messageBodyWrapper.appendChild(headerElement);
        
        // Create content element for actual message content
        targetMessageElement = document.createElement("div");
        targetMessageElement.className = "message-content";
        messageBodyWrapper.appendChild(targetMessageElement);
    } else {
        targetMessageElement = messageWrapper.querySelector(".message-content");
    }

    // If the message buffer for this ID doesn't exist, create it
    if (!messageBuffers[data.id]) {
        messageBuffers[data.id] = "";
    }

    // Store header info on first chunk and update header element
    if (data.is_first_chunk && data.username && data.model_name) {
        messageHeaders[data.id] = {
            username: data.username,
            model_name: data.model_name
        };
        
        // Update header element
        const headerElement = messageWrapper.querySelector(".message-header");
        if (headerElement) {
            const headerContent = `**${data.username} (${data.model_name}):**`;
            headerElement.innerHTML = DOMPurify.sanitize(marked.marked(headerContent), dompurify_config);
        }
    }

    // Append the chunk to the buffer
    messageBuffers[data.id] += data.content;
    
    // Process just the content and set it in the content element
    const sanitizedContent = DOMPurify.sanitize(marked.marked(messageBuffers[data.id]), dompurify_config);
    targetMessageElement.innerHTML = sanitizedContent;

    // Store the raw markdown in a data attribute for later use in editing (without header for clean editing)
    targetMessageElement.dataset.rawMarkdown = messageBuffers[data.id];

    // Apply syntax highlighting to code blocks within the content
    // Don't truncate streaming messages - only truncate on page load (previous_messages)
    targetMessageElement.querySelectorAll("pre code").forEach((block) => {
        hljs.highlightElement(block);
        addLineNumbers(block);
        addCopyButtonToCodeBlock(block, false);
    });

    // Scroll to the bottom of the chat container, but skip it if the user has scrolled up.
    if (!userHasScrolledUp) {
        document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;
    }

    // Check if the message is complete and add buttons if they haven't been added
    if (data.is_complete && !messageWrapper.querySelector(".button-container")) {
        // Create a container for the buttons
        const buttonContainer = document.createElement("div");
        buttonContainer.className = "button-container";

        // Create the delete button
        const deleteButton = document.createElement("button");
        deleteButton.innerHTML = "x";
        deleteButton.onclick = () => deleteMessage(data.id, room_name);
        buttonContainer.appendChild(deleteButton);

        // Create the edit button
        const editButton = document.createElement("button");
        editButton.textContent = "Edit";
        editButton.className = "edit-button";
        editButton.onclick = () => editMessage(data.id, targetMessageElement);
        buttonContainer.appendChild(editButton);

        // Create the copy button
        const copyButton = document.createElement("button");
        copyButton.textContent = "Copy";
        copyButton.className = "copy-button";
        copyButton.onclick = () => copyMessageContent(messageBuffers[data.id]);
        buttonContainer.appendChild(copyButton);

        // Create the play button for TTS
        const playButton = document.createElement("button");
        playButton.textContent = "Play";
        playButton.onclick = () => {
            // Use content from the content element (clean text without header)
            const cleanText = targetMessageElement.textContent || targetMessageElement.innerText || "";
            speakText(cleanText, playButton, data.id);
        };
        buttonContainer.appendChild(playButton);

        // Create the download button for TTS audio (hidden initially)
        const downloadButton = document.createElement("button");
        downloadButton.textContent = "Download";
        downloadButton.className = "tts-download-button";
        downloadButton.style.display = "none";
        downloadButton.dataset.messageId = data.id;
        buttonContainer.appendChild(downloadButton);

        // Insert the button container at the beginning of the message wrapper (before header and content)
        messageWrapper.insertBefore(buttonContainer, messageWrapper.firstChild);
        
        // Auto-play TTS if enabled and message is complete (only when streaming finishes)
        console.log("DEBUG: Streaming complete check:", {
            autoPlayTTS: autoPlayTTS,
            is_complete: data.is_complete,
            hasBuffer: !!messageBuffers[data.id],
            bufferContent: messageBuffers[data.id] ? messageBuffers[data.id].substring(0, 50) + "..." : "none",
            messageId: data.id
        });
        if (autoPlayTTS && data.is_complete && messageBuffers[data.id] && messageBuffers[data.id].trim() !== "") {
            const playButton = Array.from(buttonContainer.querySelectorAll("button")).find(btn => btn.textContent === "Play");
            console.log("DEBUG: Found play button for streaming TTS:", playButton ? "YES" : "NO", playButton?.textContent);
            if (playButton) {
                setTimeout(() => {
                    // Use content from the content element (clean text without header)
                    const cleanText = targetMessageElement.textContent || targetMessageElement.innerText || "";
                    console.log("DEBUG: Queueing streaming TTS:", data.id, cleanText.substring(0, 50) + "...");
                    queueTTS(cleanText, playButton, data.id);
                }, 50); // Small delay to let the message render
            }
        }
    }
});


// Socket event for when a message is deleted
socket.on("message_deleted", (data) => {
    const messageElement = document.getElementById("message-" + data.message_id);
    if (messageElement) {
        messageElement.remove();
    }
});

// Socket event for when a message is updated
socket.on("message_updated", (data) => {
    // Find the existing message wrapper by ID
    const messageWrapper = document.getElementById("message-" + data.message_id);

    if (messageWrapper) {
        // Find the specific element that contains the message content
        const messageContentContainer = messageWrapper.querySelector(".message-content");

        // Update the message content
        if (data.content.startsWith('<img src="data:image/jpeg;base64')) {
            // If it's a base64 image, set it directly
            messageContentContainer.innerHTML = data.content;
        } else {
            // If it's not an image, sanitize and process the message with marked
            messageContentContainer.innerHTML = DOMPurify.sanitize(marked.marked(data.content), dompurify_config);
        }

        // Update the raw markdown stored in the data attribute
        messageContentContainer.dataset.rawMarkdown = data.content;

        // Apply syntax highlighting and other functionalities to code blocks within the message
        // Don't truncate edited messages - only truncate on page load (previous_messages)
        messageContentContainer.querySelectorAll("pre code").forEach((block) => {
            hljs.highlightElement(block);
            addLineNumbers(block);
            addCopyButtonToCodeBlock(block, false);
        });
    }
});


// Function to enter edit mode
function editMessage(messageId, messageContentContainer) {
    // Store the current HTML in a data attribute
    messageContentContainer.dataset.originalHtml = messageContentContainer.innerHTML;
    const rawMarkdown = messageContentContainer.dataset.rawMarkdown;

    // Create a textarea for editing
    const textarea = document.createElement("textarea");
    textarea.value = rawMarkdown;
    textarea.rows = 16;
    textarea.className = "message-edit";

    // Replace the message content with the textarea
    messageContentContainer.innerHTML = '';
    messageContentContainer.appendChild(textarea);

    // Find the message wrapper to access the edit and save buttons
    const messageWrapper = messageContentContainer.closest('.message-wrapper');

    // Create a save button with the 'save-button' class
    const saveButton = document.createElement("button");
    saveButton.textContent = "Save";
    saveButton.className = "save-button"; // Add the class here
    saveButton.onclick = () => saveEditedMessage(messageId, textarea, messageContentContainer);

    // Change the edit button to a cancel button
    const editButton = messageWrapper.querySelector(".edit-button");
    editButton.textContent = "Cancel";
    editButton.onclick = () => cancelEdit(messageId, messageContentContainer);

    // Append the save button next to the cancel button
    editButton.after(saveButton);
}

// Function to save the edited message
function saveEditedMessage(messageId, textarea, messageContentContainer) {
    // Get the updated markdown from the textarea
    const updatedMarkdown = textarea.value;

    // Emit the update_message event to the server
    socket.emit("update_message", {
        "message_id": messageId,
        "content": updatedMarkdown,
        "room_name": room_name
    });

    // Clear the cached audio for this message to recompute TTS
    if (audioCache[messageId]) {
        delete audioCache[messageId];
    }

    // Reset the edit button to its original state
    const messageWrapper = messageContentContainer.closest('.message-wrapper');
    const editButton = messageWrapper.querySelector(".edit-button");
    editButton.textContent = 'Edit';
    editButton.onclick = () => editMessage(messageId, messageContentContainer, updatedMarkdown);

    // Reset the play button to its initial state
    const playButton = Array.from(messageWrapper.querySelectorAll("button")).find(btn => btn.textContent === 'Pause' || btn.textContent === 'Play');
    if (playButton) {
        playButton.textContent = 'Play';
        playButton.onclick = () => speakText(updatedMarkdown, playButton, messageId); // Ensure it uses the updated content
    }

    // Remove the save button using the 'save-button' class
    const saveButton = messageWrapper.querySelector(".save-button");
    if (saveButton) {
        saveButton.remove();
    }
}

// Function to cancel the edit and revert changes
function cancelEdit(messageId, messageContentContainer) {
    // Restore the original HTML of the message content from the data attribute
    messageContentContainer.innerHTML = messageContentContainer.dataset.originalHtml;

    // Reset the edit button to its original state
    const messageWrapper = messageContentContainer.closest('.message-wrapper');
    const editButton = messageWrapper.querySelector(".edit-button");
    editButton.textContent = 'Edit';
    editButton.onclick = () => editMessage(messageId, messageContentContainer, messageContentContainer.dataset.rawMarkdown);

    // Remove the save button using the 'save-button' class
    const saveButton = messageWrapper.querySelector(".save-button");
    if (saveButton) {
        saveButton.remove();
    }
}


function truncateCodeBlock(block, maxLines = 100) {
    // Split the content by new lines and check if it exceeds the maxLines
    const lines = block.textContent.split('\n');
    if (lines.length > maxLines) {
        // Store the full content in a data attribute
        block.dataset.fullContent = block.textContent;

        // Truncate the displayed content
        const truncatedText = lines.slice(0, maxLines).join('\n') + '\n...';
        block.textContent = truncatedText;

        // Store truncated text for Show More/Show Less toggle
        block.dataset.truncatedText = truncatedText;

        return true; // Indicate that the block was truncated
    }
    return false; // Indicate that the block was not truncated
}


// Modify the addCopyButtonToCodeBlock function to use the full content
function addCopyButtonToCodeBlock(block, wasTruncated = false) {
    // Check if the full content is stored in a data attribute, otherwise use textContent
    const contentToCopy = block.dataset.fullContent || block.textContent;

    // Create a container for the buttons
    const buttonContainer = document.createElement('div');
    buttonContainer.classList.add('code-block-button-container');
    buttonContainer.style.display = 'flex';
    buttonContainer.style.gap = '8px';
    buttonContainer.style.marginTop = '8px';

    // If truncated, add a "Show More" button
    if (wasTruncated) {
        const expandButton = document.createElement('button');
        expandButton.textContent = 'Show More';
        expandButton.classList.add('show-more-button');

        const truncatedText = block.dataset.truncatedText;

        expandButton.onclick = function() {
            // Restore the full content from the data attribute
            block.textContent = block.dataset.fullContent;
            // Reapply syntax highlighting
            hljs.highlightElement(block);
            addLineNumbers(block);
            // Change the button text to "Show Less"
            expandButton.textContent = 'Show Less';
            // Change the onclick function to truncate the block again
            expandButton.onclick = function() {
                block.textContent = truncatedText;
                // Reapply syntax highlighting
                hljs.highlightElement(block);
                addLineNumbers(block);
                // Change the button text back to "Show More"
                expandButton.textContent = 'Show More';
                // Set the onclick function back to the original expand function
                expandButton.onclick = originalExpandFunction;
            };
        };

        // Keep a reference to the original expand function
        const originalExpandFunction = expandButton.onclick;

        buttonContainer.appendChild(expandButton);
    }

    // Create a button to copy the code block's content
    const copyButton = document.createElement('button');
    copyButton.textContent = 'Copy';
    copyButton.classList.add('copy-button');
    copyButton.onclick = function() {
        // Copy the content to the clipboard
        navigator.clipboard.writeText(contentToCopy).then(() => {
            // Optionally, indicate that the text was copied
            copyButton.textContent = 'Copied!';
            setTimeout(() => {
                copyButton.textContent = 'Copy';
            }, 2000); // Reset button text after 2 seconds
        }).catch(err => {
            console.error('Error copying text: ', err);
        });
    };

    // Create a button to execute the code block's content
    const playButton = document.createElement('button');
    playButton.textContent = '▶ Run';
    playButton.classList.add('play-button');
    playButton.onclick = function() {
        executeCodeBlock(contentToCopy, block, playButton);
    };

    // Create a button to download compiled binary (hidden initially)
    const downloadBinaryButton = document.createElement('button');
    downloadBinaryButton.textContent = '⬇ Download Binary';
    downloadBinaryButton.classList.add('download-binary-button');
    downloadBinaryButton.style.display = 'none';

    // Add buttons to container
    buttonContainer.appendChild(copyButton);
    buttonContainer.appendChild(playButton);
    buttonContainer.appendChild(downloadBinaryButton);

    // Insert the button container after the <pre> element (not inside it)
    // block is <code>, block.parentNode is <pre>
    // We want to insert after <pre>, so we use <pre>.parentNode and <pre>.nextSibling
    const preElement = block.parentNode;
    preElement.parentNode.insertBefore(buttonContainer, preElement.nextSibling);
}

// Function to execute code block content
async function executeCodeBlock(code, blockElement, playButton) {
    // Update button state
    playButton.textContent = 'Running...';
    playButton.disabled = true;

    // Check if we already have a results container
    let resultsContainer = blockElement.parentNode.querySelector('.code-execution-results');
    if (!resultsContainer) {
        // Create results container
        resultsContainer = document.createElement('div');
        resultsContainer.classList.add('code-execution-results');
        resultsContainer.style.marginTop = '10px';
        resultsContainer.style.padding = '10px';
        resultsContainer.style.backgroundColor = 'var(--bg-code)';
        resultsContainer.style.borderRadius = '5px';
        resultsContainer.style.fontFamily = 'monospace';
        resultsContainer.style.fontSize = '14px';
        resultsContainer.style.whiteSpace = 'pre-wrap';
        resultsContainer.style.wordWrap = 'break-word';

        // Insert after the code block
        blockElement.parentNode.insertBefore(resultsContainer, blockElement.nextSibling);
    }

    // Check if this is an auto-exec from a fix and set the attempt counter
    if (blockElement.dataset.autoExecAttempt) {
        resultsContainer.dataset.fixAttempts = blockElement.dataset.autoExecAttempt;
        delete blockElement.dataset.autoExecAttempt; // Clean up after use
    }

    // Clear previous results
    resultsContainer.innerHTML = '<div style="color: var(--text-muted);">Executing code...</div>';

    try {
        // Try to detect language from the code block's class
        let language = null;
        const classes = blockElement.className.split(' ');
        for (const cls of classes) {
            if (cls.startsWith('language-')) {
                language = cls.replace('language-', '');
                break;
            }
        }

        // If no language specified (no class on code block), default to Python
        if (!language) {
            language = 'python';
        }

        // Use backend proxy for code execution (keeps API key secure)
        const asyncResponse = await fetch('/api/code/execute', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                language: language,
                code: code,
                return_artifact: true  // Request artifacts (binaries, images, videos, etc)
            })
        });

        if (!asyncResponse.ok) {
            throw new Error(`HTTP error! status: ${asyncResponse.status}`);
        }

        const { job_id } = await asyncResponse.json();

        // Poll for results: 300ms, 750ms, 1450ms, 2350ms, 3000ms, 4600ms, 6600ms+
        const delays = [300, 450, 700, 900, 650, 1600, 2000];
        let pollCount = 0;
        let cancelButtonShown = false;

        // Create cancel button (hidden initially)
        let cancelButton = resultsContainer.querySelector('.cancel-execution-btn');
        if (!cancelButton) {
            cancelButton = document.createElement('button');
            cancelButton.textContent = 'Cancel';
            cancelButton.classList.add('cancel-execution-btn');
            cancelButton.style.display = 'none';
            cancelButton.style.marginTop = '8px';
            cancelButton.style.padding = '4px 8px';
            cancelButton.style.backgroundColor = 'var(--button-danger)';
            cancelButton.style.color = 'white';
            cancelButton.style.border = 'none';
            cancelButton.style.borderRadius = '3px';
            cancelButton.style.cursor = 'pointer';
            cancelButton.onclick = async () => {
                try {
                    await fetch(`/api/code/jobs/${job_id}`, { method: 'DELETE' });
                    cancelButton.disabled = true;
                    cancelButton.textContent = 'Cancelling...';
                } catch (error) {
                    console.error('Error cancelling job:', error);
                }
            };
            resultsContainer.appendChild(cancelButton);
        }

        while (true) {
            await sleep(delays[Math.min(pollCount, delays.length - 1)]);
            pollCount++;

            const jobResponse = await fetch(`/api/code/jobs/${job_id}`);
            if (!jobResponse.ok) {
                throw new Error(`Failed to fetch job status: ${jobResponse.status}`);
            }

            const job = await jobResponse.json();

            if (job.status !== 'pending' && job.status !== 'running') {
                // Job finished - hide cancel button
                if (cancelButton) {
                    cancelButton.style.display = 'none';
                }

                if (job.status === 'completed') {
                    // Unsandbox returns stdout/stderr/exit_code at top level of job response
                    displayExecutionResults(job, resultsContainer, language, code);

                    // Check if execution failed and attempt auto-fix
                    const exitCode = job.exit_code;
                    const stderr = job.stderr || '';

                    // Only use stderr for error detection (stdout/stderr properly separated now)
                    const shouldAutoFix = exitCode !== 0 && stderr.trim() !== '';

                    // Track attempts per code block (store in resultsContainer dataset)
                    if (!resultsContainer.dataset.fixAttempts) {
                        resultsContainer.dataset.fixAttempts = '0';
                    }

                    const currentAttempts = parseInt(resultsContainer.dataset.fixAttempts);

                    if (shouldAutoFix && currentAttempts < 3) {
                        // Show auto-fix message
                        const autoFixDiv = document.createElement('div');
                        autoFixDiv.style.color = 'var(--text-info)';
                        autoFixDiv.style.fontWeight = 'bold';
                        autoFixDiv.style.marginTop = '12px';
                        autoFixDiv.textContent = `Attempting to auto-fix errors (attempt ${currentAttempts + 1}/3)...`;
                        resultsContainer.appendChild(autoFixDiv);

                        // Increment attempt counter
                        resultsContainer.dataset.fixAttempts = (currentAttempts + 1).toString();

                        // Call backend to fix the code
                        try {
                            const fixResponse = await fetch('/api/fix-code', {
                                method: 'POST',
                                headers: {
                                    'Content-Type': 'application/json',
                                },
                                body: JSON.stringify({
                                    code: code,
                                    language: language,
                                    stderr: stderr,  // Send only stderr (properly separated now)
                                    exit_code: exitCode,
                                    attempt: currentAttempts + 1
                                })
                            });

                            if (fixResponse.ok) {
                                const fixData = await fixResponse.json();

                                if (fixData.success && fixData.fixed_code) {
                                    // Update status
                                    autoFixDiv.textContent = `Code fixed! Posting and re-executing (attempt ${currentAttempts + 1}/3)...`;

                                    // Store fixed code and attempt count for auto-execution after message is posted
                                    const autoExecData = {
                                        code: fixData.fixed_code,
                                        language: language,
                                        attempt: currentAttempts + 1
                                    };

                                    // Store in global variable so chat_message handler can access it
                                    window.pendingAutoExec = autoExecData;

                                    // Post the fixed code as a new message in the chat
                                    socket.emit("chat_message", {
                                        "username": username,
                                        "message": `**Auto-fixed code (attempt ${currentAttempts + 1}/3):**\n\n\`\`\`${language}\n${fixData.fixed_code}\n\`\`\``,
                                        "model": "None",
                                        "room_name": room_name
                                    });

                                    return; // Exit - the message handler will trigger execution
                                } else {
                                    autoFixDiv.textContent = `Auto-fix failed: ${fixData.error || 'Unknown error'}`;
                                    autoFixDiv.style.color = 'var(--text-error)';
                                }
                            } else {
                                autoFixDiv.textContent = `Auto-fix request failed (HTTP ${fixResponse.status})`;
                                autoFixDiv.style.color = 'var(--text-error)';
                            }
                        } catch (autoFixError) {
                            console.error('Error during auto-fix:', autoFixError);
                            autoFixDiv.textContent = `Auto-fix error: ${autoFixError.message}`;
                            autoFixDiv.style.color = 'var(--text-error)';
                        }
                    } else if (currentAttempts >= 3 && shouldAutoFix) {
                        // Max attempts reached
                        const maxAttemptsDiv = document.createElement('div');
                        maxAttemptsDiv.style.color = 'var(--text-warning)';
                        maxAttemptsDiv.style.fontWeight = 'bold';
                        maxAttemptsDiv.style.marginTop = '12px';
                        maxAttemptsDiv.textContent = 'Maximum auto-fix attempts (3) reached. Code still has errors.';
                        resultsContainer.appendChild(maxAttemptsDiv);
                    }

                    break;
                }

                // timeout, cancelled, or failed
                const errorMsg = job.error || job.status;

                // Display whatever output we have
                displayExecutionResults(job, resultsContainer, language, code);

                // Prepend error message to the results
                const errorDiv = document.createElement('div');
                errorDiv.style.color = 'var(--text-error)';
                errorDiv.style.fontWeight = 'bold';
                errorDiv.style.marginBottom = '8px';
                errorDiv.textContent = `Execution ${errorMsg}`;
                resultsContainer.insertBefore(errorDiv, resultsContainer.firstChild);

                break;
            }

            // Show cancel button after poll #5 (3000ms) if still running
            if (!cancelButtonShown && pollCount === 5) {
                cancelButtonShown = true;
                cancelButton.style.display = 'inline-block';
            }
        }

    } catch (error) {
        console.error('Error executing code:', error);
        resultsContainer.innerHTML = `<div style="color: var(--text-error);">Error: ${escapeHtml(error.message)}</div>`;
    } finally {
        // Reset button state
        playButton.textContent = '▶ Run';
        playButton.disabled = false;
    }
}

// Helper function to sleep
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// Helper function to display execution results
function displayExecutionResults(result, resultsContainer, language, code) {
    // Format and display results
    let outputHtml = '';

    // Unsandbox API returns flat structure: {success, stdout, stderr, exit_code, artifacts}
    const actualStdout = result.stdout || '';
    const actualStderr = result.stderr || '';
    const exitCode = result.exit_code;
    const artifacts = result.artifacts || [];

    // Show language
    if (language) {
        outputHtml += `<div style="color: var(--text-info); margin-bottom: 8px;">Language: ${language}</div>`;
    }

    // Show exit code
    if (exitCode !== undefined && exitCode !== null) {
        const exitColor = exitCode === 0 ? 'var(--text-success)' : 'var(--text-error)';
        outputHtml += `<div style="color: ${exitColor}; margin-bottom: 8px;">Exit Code: ${exitCode}</div>`;
    }

    // Show stdout
    if (actualStdout) {
        outputHtml += '<div style="color: var(--text-success); font-weight: bold;">Output:</div>';
        outputHtml += `<div style="color: var(--text-primary); margin-left: 10px;">${escapeHtml(actualStdout)}</div>`;
    }

    // Show stderr if present
    if (actualStderr) {
        outputHtml += '<div style="color: var(--text-error); font-weight: bold; margin-top: 8px;">Errors/Warnings:</div>';
        outputHtml += `<div style="color: var(--text-error); margin-left: 10px;">${escapeHtml(actualStderr)}</div>`;
    }

    // If no output at all
    if (!actualStdout && !actualStderr) {
        outputHtml += '<div style="color: var(--text-muted);">(No output produced)</div>';
    }

    resultsContainer.innerHTML = outputHtml;

    // Handle artifacts (binaries, images, videos, etc.)
    if (artifacts && artifacts.length > 0) {
        const artifactsDiv = document.createElement('div');
        artifactsDiv.style.marginTop = '12px';
        artifactsDiv.style.borderTop = '1px solid var(--border-color)';
        artifactsDiv.style.paddingTop = '12px';

        const artifactsTitle = document.createElement('div');
        artifactsTitle.style.color = 'var(--text-info)';
        artifactsTitle.style.fontWeight = 'bold';
        artifactsTitle.style.marginBottom = '8px';
        artifactsTitle.textContent = `Artifacts (${artifacts.length}):`;
        artifactsDiv.appendChild(artifactsTitle);

        artifacts.forEach((artifact, index) => {
            const artifactItem = document.createElement('div');
            artifactItem.style.marginBottom = '8px';
            artifactItem.style.padding = '8px';
            artifactItem.style.backgroundColor = 'var(--bg-secondary)';
            artifactItem.style.borderRadius = '4px';

            // Artifact name/filename
            const artifactName = document.createElement('div');
            artifactName.style.fontWeight = 'bold';
            artifactName.style.marginBottom = '4px';
            artifactName.textContent = artifact.filename || artifact.name || `Artifact ${index + 1}`;
            artifactItem.appendChild(artifactName);

            // Artifact type/size info
            const mimeType = artifact.mime_type || artifact.type || '';
            if (mimeType || artifact.size) {
                const artifactInfo = document.createElement('div');
                artifactInfo.style.fontSize = '12px';
                artifactInfo.style.color = 'var(--text-muted)';
                artifactInfo.style.marginBottom = '8px';
                let infoText = '';
                if (mimeType) infoText += `Type: ${mimeType}`;
                if (artifact.size) infoText += ` | Size: ${formatFileSize(artifact.size)}`;
                artifactInfo.textContent = infoText;
                artifactItem.appendChild(artifactInfo);
            }

            // Buttons container
            const buttonsDiv = document.createElement('div');
            buttonsDiv.style.display = 'flex';
            buttonsDiv.style.gap = '8px';

            // Determine if artifact is viewable (images, videos, text)
            const isImage = mimeType.startsWith('image/');
            const isVideo = mimeType.startsWith('video/');
            const isBinary = mimeType.includes('octet-stream') || mimeType.includes('executable');

            // Download button (always available)
            const downloadBtn = document.createElement('button');
            downloadBtn.textContent = '⬇ Download';
            downloadBtn.style.padding = '4px 8px';
            downloadBtn.style.fontSize = '12px';
            downloadBtn.onclick = () => downloadArtifact(artifact);
            buttonsDiv.appendChild(downloadBtn);

            // View button (disabled for binaries)
            const viewBtn = document.createElement('button');
            viewBtn.textContent = '👁 View';
            viewBtn.style.padding = '4px 8px';
            viewBtn.style.fontSize = '12px';
            if (isBinary) {
                viewBtn.disabled = true;
                viewBtn.style.opacity = '0.5';
                viewBtn.style.cursor = 'not-allowed';
            } else {
                viewBtn.onclick = () => viewArtifact(artifact, artifactItem, isImage, isVideo);
            }
            buttonsDiv.appendChild(viewBtn);

            artifactItem.appendChild(buttonsDiv);
            artifactsDiv.appendChild(artifactItem);
        });

        resultsContainer.appendChild(artifactsDiv);
    }

    // Find the download binary button (it's in the button container next to the Run button)
    // resultsContainer is inside <pre>, button container is the next sibling of <pre>
    const preElement = resultsContainer.parentNode;
    const buttonContainer = preElement.nextSibling;
    const downloadButton = buttonContainer?.querySelector('.download-binary-button');

    // Check for compiled binary artifact
    if (result.artifact && result.artifact.type === 'base64' && result.artifact.data) {
        // Show and populate the download button
        if (downloadButton) {
            downloadButton.style.display = 'inline-block';
            downloadButton.onclick = async () => {
                try {
                    // Generate AI filename if code is available
                    let filename = result.artifact.filename || 'compiled_binary';

                    if (code && language) {
                        try {
                            const nameResponse = await fetch('/api/generate-artifact-name', {
                                method: 'POST',
                                headers: {
                                    'Content-Type': 'application/json',
                                },
                                body: JSON.stringify({
                                    code: code,
                                    language: language
                                })
                            });

                            if (nameResponse.ok) {
                                const nameData = await nameResponse.json();
                                if (nameData.filename) {
                                    filename = nameData.filename;
                                }
                            }
                        } catch (nameError) {
                            // If naming fails, fall back to original filename
                            console.warn('Failed to generate AI filename:', nameError);
                        }
                    }

                    // Convert base64 to binary
                    const binaryString = atob(result.artifact.data);
                    const bytes = new Uint8Array(binaryString.length);
                    for (let i = 0; i < binaryString.length; i++) {
                        bytes[i] = binaryString.charCodeAt(i);
                    }

                    // Create blob and download
                    const blob = new Blob([bytes], { type: 'application/octet-stream' });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                } catch (error) {
                    console.error('Error downloading binary:', error);
                    alert('Failed to download binary: ' + error.message);
                }
            };
        }
    } else {
        // Hide the download button if no artifact or artifact error
        if (downloadButton) {
            downloadButton.style.display = 'none';
        }

        // Show artifact error if present
        if (result.artifact && result.artifact.type === 'error') {
            const artifactError = document.createElement('div');
            artifactError.style.color = 'var(--text-warning)';
            artifactError.style.marginTop = '8px';
            artifactError.style.fontSize = '12px';
            artifactError.textContent = `Artifact error: ${result.artifact.error}`;
            resultsContainer.appendChild(artifactError);
        }
    }
}

// Helper function to format file size
function formatFileSize(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}

// Helper function to download artifact
async function downloadArtifact(artifact) {
    try {
        // Artifacts come as base64 in the response (field: content_base64)
        const base64Data = artifact.content_base64 || artifact.data || artifact.content;

        if (!base64Data) {
            console.error('No base64 data in artifact:', artifact);
            alert('Artifact data not available');
            return;
        }

        // Decode base64 to binary
        const binaryString = atob(base64Data);
        const bytes = new Uint8Array(binaryString.length);
        for (let i = 0; i < binaryString.length; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }

        // Determine mime type
        const mimeType = artifact.mime_type || artifact.type || 'application/octet-stream';

        // Create blob and download
        const blob = new Blob([bytes], { type: mimeType });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = artifact.filename || artifact.name || 'download';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    } catch (error) {
        console.error('Error downloading artifact:', error);
        alert('Failed to download artifact: ' + error.message);
    }
}

// Helper function to view artifact inline
async function viewArtifact(artifact, artifactItem, isImage, isVideo) {
    try {
        // Artifacts come as base64 in the response (field: content_base64)
        const base64Data = artifact.content_base64 || artifact.data || artifact.content;
        if (!base64Data) {
            console.error('No base64 data in artifact:', artifact);
            alert('Artifact data not available');
            return;
        }

        // Check if already viewing
        const existingViewer = artifactItem.querySelector('.artifact-viewer');
        if (existingViewer) {
            existingViewer.remove();
            return;
        }

        // Create viewer container
        const viewerDiv = document.createElement('div');
        viewerDiv.classList.add('artifact-viewer');
        viewerDiv.style.marginTop = '8px';
        viewerDiv.style.padding = '8px';
        viewerDiv.style.backgroundColor = 'var(--bg-primary)';
        viewerDiv.style.borderRadius = '4px';
        viewerDiv.style.maxWidth = '100%';
        viewerDiv.style.overflow = 'auto';

        // Determine mime type
        const mimeType = artifact.mime_type || artifact.type || 'application/octet-stream';

        if (isImage) {
            // Create data URL from base64
            const dataUrl = `data:${mimeType};base64,${base64Data}`;
            const img = document.createElement('img');
            img.src = dataUrl;
            img.style.maxWidth = '100%';
            img.style.height = 'auto';
            img.style.display = 'block';
            img.onerror = () => {
                viewerDiv.textContent = 'Failed to load image';
                viewerDiv.style.color = 'var(--text-error)';
            };
            viewerDiv.appendChild(img);
        } else if (isVideo) {
            // Create blob URL from base64
            const binaryString = atob(base64Data);
            const bytes = new Uint8Array(binaryString.length);
            for (let i = 0; i < binaryString.length; i++) {
                bytes[i] = binaryString.charCodeAt(i);
            }
            const blob = new Blob([bytes], { type: mimeType });
            const blobUrl = URL.createObjectURL(blob);

            const video = document.createElement('video');
            video.src = blobUrl;
            video.controls = true;
            video.style.maxWidth = '100%';
            video.style.height = 'auto';
            video.style.display = 'block';
            video.onerror = () => {
                viewerDiv.textContent = 'Failed to load video';
                viewerDiv.style.color = 'var(--text-error)';
                URL.revokeObjectURL(blobUrl);
            };
            viewerDiv.appendChild(video);
        } else {
            // For text types, decode and display
            try {
                const binaryString = atob(base64Data);
                const text = decodeURIComponent(escape(binaryString));
                const pre = document.createElement('pre');
                pre.style.margin = '0';
                pre.style.whiteSpace = 'pre-wrap';
                pre.style.wordWrap = 'break-word';
                pre.textContent = text;
                viewerDiv.appendChild(pre);
            } catch (decodeError) {
                viewerDiv.textContent = 'Failed to decode content';
                viewerDiv.style.color = 'var(--text-error)';
            }
        }

        artifactItem.appendChild(viewerDiv);
    } catch (error) {
        console.error('Error viewing artifact:', error);
        alert('Failed to view artifact: ' + error.message);
    }
}

// Helper function to escape HTML
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

function addLineNumbers(block) {
    const lines = block.textContent.split('\n').length - 1;
    const lineNumbersWrapper = document.createElement('div');
    lineNumbersWrapper.className = 'line-numbers-rows';
    for (let i = 0; i < lines; i++) {
        lineNumbersWrapper.appendChild(document.createElement('span'));
    }
    block.appendChild(lineNumbersWrapper);
}

// Socket event for setting the chat background
socket.on("set_background", (data) => {
    // Use setTimeout to ensure background updates don't get blocked by TTS
    setTimeout(() => {
        const chat = document.getElementById("chat");
        chat.style.backgroundImage = `url('data:image/png;base64,${data.image_data}')`;
        chat.style.backgroundRepeat = "no-repeat";
        chat.style.backgroundPosition = "right center";
        chat.style.backgroundSize = "auto"; // Ensures the image is not stretched
        console.log("Background image updated");
    }, 0);
});

// Activity management functions
function refreshActivityList() {
    fetch('/api/activities')
        .then(response => response.json())
        .then(data => {
            const activitySelect = document.getElementById('activity-select');
            const activitySelectMobile = document.getElementById('activity-select-mobile');
            
            // Clear existing options except the first one for desktop
            while (activitySelect.options.length > 1) {
                activitySelect.remove(1);
            }
            
            // Clear existing options except the first one for mobile
            while (activitySelectMobile.options.length > 1) {
                activitySelectMobile.remove(1);
            }
            
            // Add activities to both dropdowns
            data.activities.forEach(activity => {
                const option = document.createElement('option');
                option.value = activity;
                option.textContent = activity;
                activitySelect.appendChild(option);
                
                const optionMobile = document.createElement('option');
                optionMobile.value = activity;
                optionMobile.textContent = activity;
                activitySelectMobile.appendChild(optionMobile);
            });
        })
        .catch(error => {
            console.error('Error fetching activities:', error);
            alert('Failed to fetch activities');
        });
}

function loadSelectedActivity() {
    const activitySelect = document.getElementById('activity-select');
    const selectedActivity = activitySelect.value;
    
    if (!selectedActivity) {
        alert('Please select an activity');
        return;
    }
    
    // Send command to load activity
    socket.emit("chat_message", {
        "username": username,
        "message": `/activity ${selectedActivity}`,
        "model": document.getElementById("model-select").value,
        "room_name": room_name
    });
}

function loadSelectedActivityMobile() {
    const activitySelectMobile = document.getElementById('activity-select-mobile');
    const selectedActivity = activitySelectMobile.value;
    
    if (!selectedActivity) {
        alert('Please select an activity');
        return;
    }
    
    // Send command to load activity
    socket.emit("chat_message", {
        "username": username,
        "message": `/activity ${selectedActivity}`,
        "model": document.getElementById("model-select").value,
        "room_name": room_name
    });
}

function cancelActivity() {
    if (confirm('Are you sure you want to cancel the current activity?')) {
        socket.emit("chat_message", {
            "username": username,
            "message": "/activity cancel",
            "model": document.getElementById("model-select").value,
            "room_name": room_name
        });
    }
}

// Socket event for activity status updates
socket.on("activity_status", (data) => {
    const currentActivityInfo = document.getElementById('current-activity-info');
    const activityListSection = document.getElementById('activity-list-section');
    const currentActivityName = document.getElementById('current-activity-name');
    const currentActivityInfoMobile = document.getElementById('current-activity-info-mobile');
    const activityListSectionMobile = document.getElementById('activity-list-section-mobile');
    const currentActivityNameMobile = document.getElementById('current-activity-name-mobile');
    
    if (data.active) {
        currentActivityInfo.style.display = 'block';
        activityListSection.style.display = 'none';
        currentActivityName.textContent = data.activity_name || 'Unknown';
        currentActivityInfoMobile.style.display = 'block';
        activityListSectionMobile.style.display = 'none';
        currentActivityNameMobile.textContent = data.activity_name || 'Unknown';
    } else {
        currentActivityInfo.style.display = 'none';
        activityListSection.style.display = 'block';
        currentActivityInfoMobile.style.display = 'none';
        activityListSectionMobile.style.display = 'block';
    }
});

// Load activities on page load
document.addEventListener('DOMContentLoaded', () => {
    refreshActivityList();
    
    // Request current activity status
    socket.emit("get_activity_status", {"room_name": room_name});
});

</script>
{% endblock %}
