<div class="chat chat-end">
  <div class="chat-bubble markdown-content" data-content="{{ user_message.content|escape }}">
    {{ user_message.content|escape }}
  </div>
</div>
<div class="chat chat-start">
  <div id="ai-stream-{{user_message.id}}" data-message-id="{{user_message.id}}" hx-ext="sse"
    sse-connect="/chat/response-stream?message_id={{user_message.id}}" sse-close="close_stream"
    hx-swap="beforeend">
    <div class="chat-bubble">
      <span class="loading loading-dots loading-sm" data-stream-spinner></span>
      <div class="markdown-content" id="ai-message-content-{{user_message.id}}" sse-swap="chat_message"></div>
    </div>
    <div sse-swap="references"></div>
  </div>
</div>

<script>
  // Single delegated listener set; message identity comes from data-message-id.
  (function () {
    if (window.__streamHandlersInit) return;
    window.__streamHandlersInit = true;
    window.markdownBuffer = window.markdownBuffer || {};

    document.body.addEventListener('htmx:sseBeforeMessage', function (e) {
      const root = e.detail.elt.closest('[data-message-id]');
      if (!root) return;
      const msgId = root.dataset.messageId;
      const spinner = root.querySelector('[data-stream-spinner]');
      if (spinner) spinner.style.display = 'none';
      const el = document.getElementById('ai-message-content-' + msgId);
      if (e.detail.elt !== el) return;
      e.preventDefault();
      window.markdownBuffer[msgId] = (window.markdownBuffer[msgId] || '') + (e.detail.data || '');
      el.innerHTML = marked.parse(window.markdownBuffer[msgId].replace(/\\n/g, '\n'));
      if (typeof window.scrollChatToBottom === "function") window.scrollChatToBottom();
    });

    document.body.addEventListener('htmx:sseClose', function (e) {
      const streamEl = e.target.closest('[data-message-id]');
      if (!streamEl) return;
      const msgId = streamEl.dataset.messageId;
      const el = document.getElementById('ai-message-content-' + msgId);
      if (el && window.markdownBuffer[msgId]) {
        el.innerHTML = marked.parse(window.markdownBuffer[msgId].replace(/\\n/g, '\n'));
        delete window.markdownBuffer[msgId];
        if (typeof window.scrollChatToBottom === "function") window.scrollChatToBottom();
      }
      streamEl.removeAttribute('sse-connect');
      streamEl.removeAttribute('sse-close');
      streamEl.removeAttribute('hx-ext');
    });
  })();
</script>
