// ──────────────────────────────────────────────────────────────
//  Tech‑Soft3D  ‑  streaming Chat UI (vanilla JS, no frameworks)
//  selector‑safe version – no global $ clash with jQuery
// ──────────────────────────────────────────────────────────────

/* -------------------------------------------------------------
 *  GLOBAL CONFIG / STATE
 * ----------------------------------------------------------- */
let threadId         = null;   // server‑side thread pointer
let isWaiting        = false;  // lock UI while awaiting resp
let isPaused         = false;  // typewriter pause flag
let queue            = "";     // streaming text buffer
let shouldAutoScroll = true;   // auto‑scroll toggle
let urlRoot          = null;   // Sphinx static root
let assistantId      = null;   // filled from configMap
let vectorStore      = null;   // filled from configMap
const model     = "gpt-4.1-mini";
const typeSpeed = 10;          // ms / char
let currentController = null;  // AbortController for current request
let sharedThreadId = null;     // For handling shared thread URLs

/* -------------------------------------------------------------
 *  DOM HELPERS — safely namespaced so jQuery's $ stays intact
 * ----------------------------------------------------------- */
const qs  = sel => document.querySelector(sel);
const qsa = sel => document.querySelectorAll(sel);
const show = el => el && (el.style.display = "block");
const hide = el => el && (el.style.display = "none");

/* -------------------------------------------------------------
 *  INTRO/WELCOME HANDLING
 * ----------------------------------------------------------- */
function hideIntroScreens() {
  qsa(".welcome-screen").forEach(hide);
}

/* -------------------------------------------------------------
 *  UTILS
 * ----------------------------------------------------------- */
const cfg = project => configMap[project] || {};
const scrollToBottom = () => {
  const body = qs("#chat-body");
  if (shouldAutoScroll && body) body.scrollTop = body.scrollHeight;
};

const scrollToTop = () => {
  const body = qs("#chat-body");
  if (shouldAutoScroll && body) body.scrollTop = 0;
};

/* Update loading text dynamically */
function updateLoadingText(text) {
  const loadingTextElement = qs(".loading-text");
  if (loadingTextElement) {
    loadingTextElement.textContent = text;
  }
}

/* Get assistant name based on project */
function getAssistantName() {
  // Check if chat_project is defined and starts with 'SpinFire'
  if (typeof chat_project !== 'undefined' && chat_project.startsWith('SpinFire')) {
    return 'SPINNY';
  }
  return 'HOOPSY';
}

/* Parse markdown with custom configuration to handle dividers properly */
function parseMarkdownSafely(text) {
  if (!window.marked) return text;
  
  // Configure marked to be more predictable and handle line breaks properly
  const markedOptions = { 
    breaks: true,
    gfm: true,
    headerIds: false,
    mangle: false,
    pedantic: false,
    sanitize: false
  };
  
  // Pre-process to handle --- dividers that shouldn't become headers
  let processedText = text;
  
  // First, handle list boundaries to prevent content absorption
  processedText = processedText.replace(/([^\n])\n([-*+]\s)/g, '$1\n\n$2');
  processedText = processedText.replace(/([-*+]\s[^\n]*)\n([^\n-*+\s][^\n]*)/g, '$1\n\n$2');
  processedText = processedText.replace(/^(.+?)\s*\n---+\s*$/gm, (match, textLine) => {
    // Check if the text line looks like it should be a heading
    // If it's a long sentence or ends with punctuation, treat --- as divider
    if (textLine.length > 50 || /[.!?:]$/.test(textLine.trim())) {
      return textLine + '\n\n---';
    }
    // Otherwise, keep the original (let it be a heading)
    return match;
  });
  processedText = processedText.replace(/(\|[^\n]*\|\s*\n)+(?!\s*\|)/g, (match) => {
    // If the match doesn't end with a blank line, add one
    if (!match.endsWith('\n\n')) return match + '\n';
    return match;
  });
  processedText = processedText.replace(/(\|[^\n]*\|\s*\n)([^|\n\s][^\n]*)/g, '$1\n$2');

  // Resolve _static image paths using Path_ROOT (jinja url_root) or urlRoot fallback
  function resolveStaticHref(href) {
    if (typeof href !== 'string' || !href) return href;
    // absolute and data URLs untouched
    if (/^([a-z]+:)?\/\//i.test(href) || href.startsWith('data:') || href.startsWith('mailto:')) return href;

    // If path references _static anywhere, prefix with Path_ROOT (trim leading slashes)
    if (href.includes('_static')) {
      const base = (typeof Path_ROOT !== 'undefined' && Path_ROOT) ? Path_ROOT : (urlRoot || '/');
      const clean = href.replace(/^\/+/, ''); // avoid domain-root absolute /_static
      return base + clean;
    }
    return href;
  }

  // Use walkTokens to safely rewrite image token hrefs (avoids renderer signature issues)
  markedOptions.walkTokens = (token) => {
    if (token.type === 'image' && token.href != null) {
      token.href = resolveStaticHref(token.href);
    }
    // Also fix HTML tokens with <img src="..."> where possible (light-touch)
    if (token.type === 'html' && typeof token.text === 'string' && token.text.includes('<img')) {
      token.text = token.text.replace(/(<img\b[^>]*\bsrc=["'])([^"']+)(["'][^>]*>)/gi, (m, p1, src, p3) => {
        return p1 + resolveStaticHref(src) + p3;
      });
    }
  };

  return marked.parse(processedText, markedOptions);
}


/* -------------------------------------------------------------
 *  STREAMING FETCH WRAPPER (Lambda ↔️ OpenAI)
 * ----------------------------------------------------------- */
async function chatStream(payload, {signal, onMeta, onDelta, onDone }) {
  const resp = await fetch("https://yivtrivakoskj4a7grxr37m6n40kubhr.lambda-url.eu-west-3.on.aws/", {
    method : "POST",
    headers: { "Content-Type": "application/json" },
    body   : JSON.stringify(payload),
    signal
  });
  
  if (!resp.ok) {
    // Try to get error details from response
    let errorData = null;
    try {
      const errorText = await resp.text();
      if (errorText) {
        try {
          errorData = JSON.parse(errorText);
        } catch {
          errorData = { error: errorText, statusCode: resp.status };
        }
      }
    } catch {
      errorData = { error: `HTTP ${resp.status}`, statusCode: resp.status };
    }
    
    throw new Error(JSON.stringify(errorData));
  }

  const reader  = resp.body.getReader();
  const decoder = new TextDecoder();
  let   buf     = "";

  while (true) {
  const { value, done } = await reader.read();

  // For the very last read() call `value` can be undefined, so give
  // TextDecoder an empty Uint8Array instead.
  buf += decoder.decode(value ?? new Uint8Array(), { stream: !done });

  // Split what we have so far into lines
  let lines = buf.split("\n");

  // While the stream is still open keep the incomplete last line in `buf`
  // so the next chunk can complete it.  When `done` is true we want to
  // flush everything, so we leave `buf` empty.
  buf = done ? "" : lines.pop();

  // Process every complete line we just obtained
  for (const line of lines) {
    if (!line.trim()) continue;
    if (line.trim().startsWith("{")) {
      try {
        const obj = JSON.parse(line);
        // Check if this is an error response
        if (obj.error) {
          throw new Error(JSON.stringify(obj));
        }
        if (obj.meta)       onMeta(obj.meta);
        else if (obj.done)  onDone(obj);   // <- you'll get {"done":true} here
      } catch (jsonError) {
        // If it's already our error object, re-throw it
        if (jsonError.message.includes('"error"')) {
          throw jsonError;
        }
        // If JSON parsing fails, treat it as a delta (text content)
        console.warn('JSON parse failed, treating as text:', jsonError.message);
        onDelta(line + "\n");
      }
    } else {
      onDelta(line + "\n");
    }
  }

  if (done) break;     // we have already flushed the final line(s)
}
}

/* -------------------------------------------------------------
 *  DOM BUILDERS
 * ----------------------------------------------------------- */
function addMessage(role, initial = "") {
  const wrap = document.createElement("div");
  wrap.className = `message ${role}`;
  const txt = document.createElement("div");
  txt.className  = "text";
  txt.innerText  = initial;
  wrap.appendChild(txt);
  qs("#chat-body").insertBefore(wrap, qs("#loadingIndicator"));
  return { wrap, txt };
}

/* Create error message for API failures */
function addErrorMessage(errorText) {
  const wrap = document.createElement("div");
  wrap.className = "message assistant error";
  const txt = document.createElement("div");
  txt.className = "text";
  txt.innerText = errorText;
  wrap.appendChild(txt);
  qs("#chat-body").insertBefore(wrap, qs("#loadingIndicator"));
  return { wrap, txt };
}

/* Convert technical errors to user-friendly messages */
function getFriendlyErrorMessage(error) {
  // Handle different error formats
  let errorMessage = "";
  
  if (typeof error === 'string') {
    errorMessage = error;
  } else if (error && error.error) {
    errorMessage = error.error;
  } else if (error && error.message) {
    errorMessage = error.message;
  } else {
    errorMessage = "An unexpected error occurred";
  }
  
  // Convert technical errors to user-friendly messages
  const errorLower = errorMessage.toLowerCase();
  
  if (errorLower.includes('temperature') || errorLower.includes('unsupported parameter')) {
    return "I'm having trouble with my settings right now. Please try again in a moment.";
  } else if (errorLower.includes('rate limit') || errorLower.includes('quota')) {
    return "I'm getting a lot of requests right now. Please wait a moment and try again.";
  } else if (errorLower.includes('network') || errorLower.includes('connection')) {
    return "I'm having trouble connecting right now. Please check your internet connection and try again.";
  } else if (errorLower.includes('timeout')) {
    return "That request took too long to process. Please try again with a shorter message.";
  } else if (errorLower.includes('unauthorized') || errorLower.includes('authentication')) {
    return "There's a temporary access issue. Please try again in a few minutes.";
  } else if (errorLower.includes('server error') || errorLower.includes('internal error')) {
    return "I'm experiencing technical difficulties. Please try again later.";
  } else if (errorLower.includes('bad request') || errorLower.includes('invalid')) {
    return "There was an issue processing your request. Please try rephrasing your question.";
  } else {
    return "I'm having trouble processing your request right now. Please try again.";
  }
}

/* Create rating buttons for assistant messages */
function createRatingButtons(bubble, references) {
    // Create a container for the first ref and rating buttons to be on the same row
    const refsRatingContainer = document.createElement('div');
    refsRatingContainer.className = 'refs-rating-container';
    bubble.wrap.appendChild(refsRatingContainer);
    
    // Add rating buttons for assistant messages (hidden initially)
    const ratingContainer = document.createElement('div');
    ratingContainer.className = 'rating-buttons rating-hidden';
    
    // Add feedback text (hidden initially)
    const feedbackText = document.createElement('span');
    feedbackText.className = 'rating-feedback';
    feedbackText.textContent = '';
    
    const thumbsUpBtn = document.createElement('button');
    thumbsUpBtn.className = 'rating-btn thumbs-up';
    thumbsUpBtn.title = 'Good response';
    thumbsUpBtn.innerHTML = '<i class="fa-solid fa-thumbs-up"></i>';
    thumbsUpBtn.onclick = () => rateMessage(bubble.wrap, 'thumbs-up');
    
    const thumbsDownBtn = document.createElement('button');
    thumbsDownBtn.className = 'rating-btn thumbs-down';
    thumbsDownBtn.title = 'Bad response';
    thumbsDownBtn.innerHTML = '<i class="fa-solid fa-thumbs-down"></i>';
    thumbsDownBtn.onclick = () => rateMessage(bubble.wrap, 'thumbs-down');
    
    ratingContainer.appendChild(feedbackText);
    ratingContainer.appendChild(thumbsUpBtn);
    ratingContainer.appendChild(thumbsDownBtn);
    refsRatingContainer.appendChild(ratingContainer);
    bubble.wrap.appendChild(refsRatingContainer);
    
    // If there are no references, move rating buttons to refs-rating container
    if (references.length === 0) {
        const refsRatingContainer = bubble.wrap.querySelector('.refs-rating-container');
        const ratingContainer = bubble.wrap.querySelector('.rating-buttons');
        if (refsRatingContainer && ratingContainer) {
            // Remove from current location and add to refs-rating container
            ratingContainer.remove();
            refsRatingContainer.appendChild(ratingContainer);
            refsRatingContainer.style.justifyContent = 'flex-end';
        }
    }
}

/* -------------------------------------------------------------
 *  TYPEWRITER FOR STREAMING
 * ----------------------------------------------------------- */
function pumpQueue(bubble) {
  if (bubble.typing || queue.length === 0 || isPaused) return;

  bubble.typing = true;

  (function tick() {
    if (queue.length === 0 || isPaused) {    // finished or paused
      bubble.typing = false;
      return;
    }

    /* 1. take ONE char, just like the old code */
    const char = queue[0];
    queue = queue.slice(1);

    /* 2. append it to the raw markdown buffer */
    bubble.raw = (bubble.raw || "") + char;

    /* 3. schedule a markdown re-render once this frame */
    if (!bubble._raf) {
      bubble._raf = requestAnimationFrame(() => {
        try {
          // Pre-process raw text to ensure line breaks are preserved during streaming
          let processedText = bubble.raw;
          
          // First, ensure proper spacing around lists to prevent content absorption
          // Add blank line before lists if there isn't one
          processedText = processedText.replace(/([^\n])\n([-*+]\s)/g, '$1\n\n$2');
          
          // Add blank line after lists if the next content isn't a list item or blank line
          processedText = processedText.replace(/([-*+]\s[^\n]*)\n([^\n-*+\s][^\n]*)/g, '$1\n\n$2');
          
          // Convert single newlines to markdown hard line breaks (two spaces + newline)
          // Look for a non-newline character followed by single newline followed by non-newline
          // But avoid affecting list items and their content
          processedText = processedText.replace(/([^\n])\n(?!\n)(?![-*+]\s)([^\n])/g, '$1  \n$2');
          
          bubble.txt.innerHTML = parseMarkdownSafely(processedText);
          
          // Apply syntax highlighting and copy buttons to any new code blocks immediately
          const codeBlocks = bubble.wrap.querySelectorAll('pre code');
          codeBlocks.forEach(codeElement => {
            // Apply syntax highlighting if not already done
            if (window.hljs && !codeElement.dataset.highlighted) {
              hljs.highlightElement(codeElement);
              codeElement.dataset.highlighted = 'true';
            }
          });
          
          // Add copy buttons to any new code blocks
          addCodeCopyButtons(bubble.wrap);
          
        } catch (error) {
          // Fallback to simple line break conversion for streaming
          console.warn('Markdown parsing error during streaming:', error);
          bubble.txt.innerHTML = bubble.raw.replace(/\n/g, '<br>');
        }
        bubble._raf = null;
        scrollToBottom();                   // same helper you used before
      });
    }

    /* 4. keep typing */
    setTimeout(tick, typeSpeed);
  })();
}

/* Handle streaming text delta from server */
const handleDelta = (bubble, chunk) => { 
  // Remove OpenAI citation syntaxes before adding to queue
  const cleanedChunk = removeCitationSyntax(chunk);
  queue += cleanedChunk; 
  pumpQueue(bubble); 
};

/* Remove OpenAI citation syntax patterns like 【5:1-7†general#technical_overview.txt】 */
function removeCitationSyntax(text) {
  if (!text || typeof text !== 'string') return text;
  
  // Pattern to match OpenAI citations: 【...†...】
  // Uses Unicode brackets (U+3010, U+3011) and dagger symbol (U+2020)
  const citationPattern = /【[^】]*†[^】]*】/g;
  
  // Also catch partial patterns that might appear during streaming
  const partialPatterns = [
    /【[^】]*$/g,           // Opening bracket with content but no closing
    /^[^【]*†[^】]*】/g,    // Closing part with dagger and closing bracket
    /【[^】]*†[^】]*$/g     // Opening with dagger but no closing bracket
  ];
  
  let cleaned = text.replace(citationPattern, '');
  
  // Remove partial patterns (these might appear split across chunks)
  partialPatterns.forEach(pattern => {
    cleaned = cleaned.replace(pattern, '');
  });
  
  return cleaned;
}

/* Clean citation syntax from complete text (for final processing) */
function cleanCitationSyntaxFromText(text) {
  if (!text || typeof text !== 'string') return text;
  
  // More comprehensive cleaning for complete text
  const patterns = [
    /【[^】]*†[^】]*】/g,     // Full citation pattern
    /【[^】]*】/g,           // Any content in Unicode brackets
    /†/g                    // Standalone dagger symbols
  ];
  
  let cleaned = text;
  patterns.forEach(pattern => {
    cleaned = cleaned.replace(pattern, '');
  });
  
  // Clean up any double spaces left behind
  cleaned = cleaned.replace(/\s{2,}/g, ' ').trim();
  
  return cleaned;
}

/* -------------------------------------------------------------
 *  CHAT CONTAINER DRAG FUNCTIONALITY (Desktop only)
 * ----------------------------------------------------------- */
// Make the chat container header draggable within the viewport (desktop only)
const isDesktop = window.innerWidth > 768 && !('ontouchstart' in window);
if (isDesktop) { // Only enable dragging on desktop devices without touch
    interact('.chat-header').draggable({
        inertia: false,
        modifiers: [
            interact.modifiers.restrict({
                restriction: 'parent',
                endOnly: true,
            }),
            interact.modifiers.restrictEdges({
                outer: 'parent',
                endOnly: true,
            })
        ],
        listeners: {
            move(event) {
                const target = event.target.closest('.chat-container');
                if (!target) return;
                
                let x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
                let y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

                // Constrain to viewport
                const rect = target.getBoundingClientRect();
                const viewportWidth = window.innerWidth;
                const viewportHeight = window.innerHeight;
                
                // Prevent dragging outside viewport
                if (rect.left + event.dx < 20) x = 20 - rect.left + (parseFloat(target.getAttribute('data-x')) || 0);
                if (rect.right + event.dx > viewportWidth - 20) x = (viewportWidth - 20 - rect.right) + (parseFloat(target.getAttribute('data-x')) || 0);
                if (rect.top + event.dy < 20) y = 20 - rect.top + (parseFloat(target.getAttribute('data-y')) || 0);
                if (rect.bottom + event.dy > viewportHeight - 20) y = (viewportHeight - 20 - rect.bottom) + (parseFloat(target.getAttribute('data-y')) || 0);

                target.style.transform = `translate(${x}px, ${y}px)`;
                target.setAttribute('data-x', x);
                target.setAttribute('data-y', y);
            }
        }
    });
} else {
}

/* -------------------------------------------------------------
 *  MESSAGE FINALISE (markdown + refs + rating)
 * ----------------------------------------------------------- */
async function finishAssistant(bubble, { references = [], vs_id, rated, rating, skipMarkdownReparse = false }) {
  // Wait until the typewriter drained the queue
  while (bubble._typing || queue.length) {
    await new Promise(r => setTimeout(r, 16));
  }

  // If user pressed stop, do not finalize UI (no refs/rating)
  if (isPaused) {
    setSendButtonState("send");
    resetUIState();
    return;
  }

  // Final markdown pass - skip for loaded threads as they're already parsed correctly
  if (window.marked && !skipMarkdownReparse) {
    // Apply the same line break preprocessing as during streaming
    let processedText = bubble.raw || "";
    
    // Clean citation syntax from the final text
    processedText = cleanCitationSyntaxFromText(processedText);
    
    // First, preserve existing double line breaks (paragraph breaks)
    processedText = processedText.replace(/\n\n/g, '\n\n');
    
    // Convert single newlines to markdown hard line breaks (two spaces + newline)
    // This regex handles single newlines that aren't part of double newlines
    processedText = processedText.replace(/([^\n])\n([^\n])/g, '$1  \n$2');
    
    bubble.txt.innerHTML = parseMarkdownSafely(processedText);
  }
  
  // Final highlighting pass for any remaining unhighlighted code blocks
  if (window.hljs) {
    bubble.wrap.querySelectorAll("pre code").forEach(c => {
      if (!c.dataset.highlighted) {
        hljs.highlightElement(c);
        c.dataset.highlighted = 'true';
      }
    });
  }
  
  // Ensure all code blocks have copy buttons
  addCodeCopyButtons(bubble.wrap);
  
  createRatingButtons(bubble, references);

  // If this message was already rated (e.g., loaded from history), reflect that in UI and disable further rating
  if (rated) {
    const ratingContainer = bubble.wrap.querySelector('.rating-buttons');
    if (ratingContainer) {
      // Normalize rating value to our class naming convention
      const norm = (rating || '').toString().toLowerCase().replace('_', '-'); // thumbs_up -> thumbs-up
      const feedbackText = ratingContainer.querySelector('.rating-feedback');
      const upBtn = ratingContainer.querySelector('.rating-btn.thumbs-up');
      const downBtn = ratingContainer.querySelector('.rating-btn.thumbs-down');

      // Apply active state
      if (norm === 'thumbs-up' && upBtn) {
        upBtn.classList.add('active');
      } else if (norm === 'thumbs-down' && downBtn) {
        downBtn.classList.add('active');
      }

      // Mark container as rated and set feedback text
      ratingContainer.classList.add('rated');
      if (feedbackText) {
        feedbackText.textContent = norm === 'thumbs-up' ? 'Good Response' : 'Bad Response';
      }

      // Disable further interaction
      [upBtn, downBtn].forEach(btn => {
        if (btn) {
          btn.disabled = true;
          btn.style.pointerEvents = 'none';
          btn.onclick = null;
        }
      });
    }
  }

// Handle references with vs_id if available
    for (let i = 0; i < references.length; i++) {
        const url = references[i];
        let baseUrl = urlRoot;
        
        // Always use the vs_id for references if available to ensure they point to the correct product documentation
        if (vs_id) {
            const vsSpecificUrl = getDocUrlFromVsId(vs_id);
            if (vsSpecificUrl) {
                baseUrl = vsSpecificUrl;
            }
        }
        const fullUrl = baseUrl + url;
        const title = await fetchHtmlTitle(fullUrl);
        const linkHolder = document.createElement('div');
        // linkHolder.innerText = `${title}`; // REMOVE: caused duplicate text
        linkHolder.className = 'chat-ref';
        linkHolder.setAttribute('title', title);       // tooltip
        linkHolder.setAttribute('aria-label', title);  // a11y
        linkHolder.onclick = () => window.open(fullUrl, '_blank');

        // Visible text label (kept)
        const label = document.createElement('span');
        label.className = 'chat-ref__label';
        label.textContent = title;
        linkHolder.appendChild(label);

        // For the first reference, add it to the refs-rating container if it exists
        if (i === 0) {
            const refsRatingContainer = bubble.wrap.querySelector('.refs-rating-container');
            if (refsRatingContainer) {
                // Insert the first ref at the beginning of the container (before rating buttons)
                const ratingButtons = refsRatingContainer.querySelector('.rating-buttons');
                if (ratingButtons) {
                    refsRatingContainer.insertBefore(linkHolder, ratingButtons);
                } else {
                    refsRatingContainer.appendChild(linkHolder);
                }
            } else {
                bubble.wrap.appendChild(linkHolder);
            }
        } else {
            // For subsequent references, add them to a separate container or directly to message
            let additionalRefsContainer = bubble.wrap.querySelector('.additional-refs-container');
            if (!additionalRefsContainer) {
                additionalRefsContainer = document.createElement('div');
                additionalRefsContainer.className = 'additional-refs-container';
                additionalRefsContainer.style.gridArea = 'additional-refs';
                bubble.wrap.appendChild(additionalRefsContainer);
            }
            additionalRefsContainer.appendChild(linkHolder);
        }
    }
  // Show share, new conversation, and summarize buttons after assistant response
  const shareBtn = qs("#share-conversation-button");
  const newConvBtn = qs("#new-conversation-btn");
  const summarizeBtn = qs("#summarize-page-button");
  if (shareBtn && threadId) shareBtn.style.display = "inline-flex";
  if (newConvBtn) newConvBtn.style.display = "inline-flex";
  if (summarizeBtn) summarizeBtn.style.display = "inline-flex";
  updateActionButtonsLayout();
  
  const ratingContainer = bubble.wrap.querySelector('.rating-buttons');
  if (ratingContainer) ratingContainer.classList.remove('rating-hidden');

  resetUIState();
}

/* -------------------------------------------------------------
 *  CODE COPY FUNCTIONALITY
 * ----------------------------------------------------------- */
function addCodeCopyButtons(messageElement) {
  const codeBlocks = messageElement.querySelectorAll('pre code');
  
  codeBlocks.forEach(codeBlock => {
    const preElement = codeBlock.parentElement;
    
    // Skip if copy button already exists
    if (preElement.querySelector('.code-copy-btn')) return;
    
    // Create copy button
    const copyBtn = document.createElement('button');
    copyBtn.className = 'code-copy-btn';
    copyBtn.title = 'Copy code';
    copyBtn.innerHTML = '<i class="fa-solid fa-copy"></i>';
    
    // Add click handler
    copyBtn.addEventListener('click', () => copyCodeToClipboard(codeBlock, copyBtn));
    
    // Add button to pre element
    preElement.appendChild(copyBtn);
  });
}

function copyCodeToClipboard(codeElement, buttonElement) {
  const codeText = codeElement.textContent;
  
  // Try modern clipboard API first
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(codeText).then(() => {
      showCopyNotification();
      animateCopyButton(buttonElement);
    }).catch(() => {
      // Fallback to older method
      fallbackCopyToClipboard(codeText, buttonElement);
    });
  } else {
    // Use fallback method
    fallbackCopyToClipboard(codeText, buttonElement);
  }
}

function fallbackCopyToClipboard(text, buttonElement) {
  const textArea = document.createElement('textarea');
  textArea.value = text;
  textArea.style.position = 'fixed';
  textArea.style.left = '-999999px';
  textArea.style.top = '-999999px';
  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();
  
  try {
    const successful = document.execCommand('copy');
    document.body.removeChild(textArea);
    if (successful) {
      showCopyNotification();
      animateCopyButton(buttonElement);
    }
  } catch (err) {
    document.body.removeChild(textArea);
    console.error('Failed to copy code:', err);
  }
}

function showCopyNotification() {
  // Remove existing notification if any
  const existing = document.querySelector('.copy-notification');
  if (existing) existing.remove();
  
  // Create notification
  const notification = document.createElement('div');
  notification.className = 'copy-notification';
  notification.innerHTML = '📋 Code copied to clipboard!';
  
  document.body.appendChild(notification);
  
  // Show notification
  setTimeout(() => notification.classList.add('show'), 10);
  
  // Hide and remove notification after 3 seconds
  setTimeout(() => {
    notification.classList.add('hide');
    setTimeout(() => notification.remove(), 300);
  }, 3000);
}

function animateCopyButton(buttonElement) {
  // Temporarily change icon to checkmark
  const originalHTML = buttonElement.innerHTML;
  buttonElement.innerHTML = '<i class="fa-solid fa-check"></i>';
  buttonElement.style.color = '#4CAF50';
  
  // Revert after 1.5 seconds
  setTimeout(() => {
    buttonElement.innerHTML = originalHTML;
    buttonElement.style.color = '';
  }, 1500);
}

/* -------------------------------------------------------------
 *  UI STATE HELPERS
 * ----------------------------------------------------------- */
function setSendButtonState(state) {
    const sendIcon = document.getElementById('send-icon');
    const loadingIcon = document.getElementById('loading-icon');
    const stopIcon = document.getElementById('stop-icon');
    const sendButton = document.getElementById('send-button');
    
    // Reset all states first
    if (sendIcon) sendIcon.style.display = 'none';
    if (loadingIcon) loadingIcon.style.display = 'none';
    if (stopIcon) stopIcon.style.display = 'none';
    if (sendButton) sendButton.classList.remove('stop-active');
    
    switch(state) {
        case 'send':
            if (sendIcon) sendIcon.style.display = 'block';
            break;
        case 'loading':
            if (loadingIcon) loadingIcon.style.display = 'block';
            break;
        case 'stop':
            if (stopIcon) stopIcon.style.display = 'flex';
            if (sendButton) sendButton.classList.add('stop-active');
            break;
    }
}

function resetUIState() {
    // Enable input and reset UI elements
    const sendButton = document.getElementById('send-button');
    const userInputElement = document.getElementById('user-input');
    setSendButtonState('send');
    sendButton.classList.remove('active');
    userInputElement.disabled = false;
    sendButton.disabled = false;
    isWaiting = false;  
    isPaused = false;
    
    // Stop typing animation
    stopTypingAnimation();
  // Keep char counter in sync with current input contents
  if (typeof checkInput === 'function') {
    checkInput();
  }
}

/* -------------------------------------------------------------
 *  SEND MESSAGE HANDLER
 * ----------------------------------------------------------- */
async function sendMessage(prefill = null) {
  isPaused = false;  
  queue    = "";
  if (isWaiting) return;
  const inputEl = qs("#user-input");
  const textVal = (prefill || inputEl.value).trim();
  if (!textVal) return; // <-- Don't disable input if empty

  inputEl.disabled = true; // <-- Only disable after non-empty input
  hideIntroScreens();
  const userBubble = addMessage("user", textVal);
  // Align the user's question to the top of the chat body and disable auto-scroll for the assistant reply
  const body = qs('#chat-body');
  if (body && userBubble && userBubble.wrap) {
    const bodyRect = body.getBoundingClientRect();
    const bubbleRect = userBubble.wrap.getBoundingClientRect();
    const targetScrollTop = body.scrollTop + (bubbleRect.top - bodyRect.top);
    body.scrollTop = targetScrollTop; // place user's bubble at the very top of the chat view
    shouldAutoScroll = false; // prevent auto-scrolling while the assistant streams
  }
  inputEl.value = "";
  // Reflect clear in char counter immediately
  if (typeof checkInput === 'function') {
    checkInput();
  }
  isWaiting = true;
  setSendButtonState("loading");

  // Ensure spinner is shown before request with default thinking text
  const spinner = qs("#loadingIndicator");
  if (spinner) {
    updateLoadingText(`${getAssistantName()} is thinking...`);
    spinner.style.display = "block";
  }

  // Hide share, new conversation, and summarize buttons until assistant response
  const summarizeBtn = qs("#summarize-page-button");
  const newConvBtn = qs("#new-conversation-btn");
  if (summarizeBtn) summarizeBtn.style.display = "inline-flex";
  if (newConvBtn) newConvBtn.style.display = "inline-flex";
  updateActionButtonsLayout();

  // Only show loading indicator before assistant response
  let bubble = null;

  const payload = {
    action     : "sendMessage",
    userMessage: textVal,
    threadId,
    model,
    assistantId,
    vectorstore: vectorStore,
    product    : chat_project
  };
  
  if (currentController) currentController.abort();

  // create a fresh controller for this request
  currentController = new AbortController();
  const { signal } = currentController;

  try {
    await chatStream(payload, {signal,
      onMeta(meta) {
        threadId = meta.thread_id;
        localStorage.setItem("threadData", JSON.stringify({ thread_id: threadId, timestamp: Date.now() }));
        const shareBtn = qs("#share-conversation-button");
        if (shareBtn) shareBtn.style.display = "inline-flex";
        // Update loading text when receiving response metadata
        updateLoadingText("Formulating response...");
        setSendButtonState("stop");
  updateActionButtonsLayout();
      },
      onDelta(chunk) {
        if (isPaused) return;
        // On first delta, hide loading indicator and show assistant bubble
        if (!bubble) {
          hide(qs("#loadingIndicator")); // hide spinner when assistant starts replying
          bubble = addMessage("assistant");
          // Start pulsing RGB border exactly when typewriter begins
          startTypingAnimation();
        }
        handleDelta(bubble, chunk);
      },
      onDone(obj) {
          if (isPaused) {          // user pressed Stop → skip finalisation
            resetUIState();             // just unlock UI and leave the partial answer
            return;
          }
          finishAssistant(bubble, obj);
        bubble.wrap.dataset.messageId = obj.message_id;
      }
    });
  } catch (err) {
    if (err.name !== "AbortError"){    
        console.error(err);
        
        // Hide loading indicator
        hide(qs("#loadingIndicator"));
        
        // Parse the error and show user-friendly message
        let errorData = null;
        try {
          // Try to parse JSON error response
          if (err.message && err.message.includes('{')) {
            const jsonStart = err.message.indexOf('{');
            const jsonStr = err.message.substring(jsonStart);
            errorData = JSON.parse(jsonStr);
          }
        } catch (parseErr) {
          // If parsing fails, use the original error
          errorData = err;
        }
        
        const friendlyMessage = getFriendlyErrorMessage(errorData || err);
        addErrorMessage(friendlyMessage);
        
        resetUIState();
    }
  }finally {
    currentController = null;
  }
}

/* -------------------------------------------------------------
 *  EVENT HANDLERS AND UI FUNCTIONS
 * ----------------------------------------------------------- */

// Function to detect if user scrolls manually
function handleUserScroll() {
    const chatBody = document.getElementById('chat-body');
    const scrollTop = chatBody.scrollTop;
    const scrollHeight = chatBody.scrollHeight;
    const clientHeight = chatBody.clientHeight;
    // Check if the user is at the bottom (allowing a small buffer)
    shouldAutoScroll = (scrollTop + clientHeight >= scrollHeight - 10);
}

/* Check input and update UI accordingly */
function checkInput() {
    const userInput = document.getElementById('user-input');
    const sendButton = document.getElementById('send-button');

    if (userInput.value.trim() !== '') {
        sendButton.classList.add('active');
    } else {
        sendButton.classList.remove('active');
    }
    
    // Update character counter
    var maxLength = 1000;
    var currentLength = userInput.value.length;
    var remaining = maxLength - currentLength;
    const charCountElement = document.getElementById('char-count');
    if (charCountElement) {
        if (currentLength > 0) {
            charCountElement.style.display = 'block';
            charCountElement.innerText = `${currentLength}/${maxLength}`;
        } else {
            charCountElement.style.display = 'none';
        }
    }
}

// Function to interrupt the interface typing process
function stopTyping() {
    if (currentController) {
        currentController.abort();
        currentController = null;
    }
    isPaused = true;
    isWaiting = false;
    setSendButtonState('send');
    const userInputElement = document.getElementById('user-input');
    const sendButton = document.getElementById('send-button');
    sendButton.classList.remove('active');
    userInputElement.disabled = false;
    sendButton.disabled = false;
    
    // Stop typing animation
    stopTypingAnimation();
  // Ensure char counter reflects current input value
  if (typeof checkInput === 'function') {
    checkInput();
  }
}

/* Share conversation functionality */
function shareConversation() {
    if (!threadId) {
        alert("No conversation to share!");
        return;
    }

    const currentUrl = window.location.href.split('?')[0]; // Remove existing query parameters
    const shareableLink = `${currentUrl}?thread_id=${encodeURIComponent(threadId)}`;
    const shareButton = document.querySelector("#share-conversation-button");
    const shareIcon = document.getElementById('share-icon');
    const shareCheck = document.getElementById('share-check');

    // Function to show success feedback
    function showSuccessFeedback() {
        if (shareIcon) shareIcon.style.display = 'none';
        if (shareCheck) shareCheck.style.display = 'block';

        // After 2 seconds, revert back
        setTimeout(function() {
            if (shareIcon) shareIcon.style.display = 'block';
            if (shareCheck) shareCheck.style.display = 'none';
        }, 2000);
    }

    // Function to copy using fallback method (more reliable for Firefox)
    function fallbackCopyToClipboard(text) {
        const textArea = document.createElement("textarea");
        textArea.value = text;
        textArea.style.position = "fixed";
        textArea.style.left = "-999999px";
        textArea.style.top = "-999999px";
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();
        
        try {
            const successful = document.execCommand('copy');
            document.body.removeChild(textArea);
            return successful;
        } catch (err) {
            document.body.removeChild(textArea);
            return false;
        }
    }

    // Detect Firefox for special handling
    const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
    
    // For Firefox, use fallback method first as it's more reliable
    if (isFirefox) {
        if (fallbackCopyToClipboard(shareableLink)) {
            showSuccessFeedback();
            return;
        }
        // If fallback fails, try modern API
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(shareableLink).then(function() {
                showSuccessFeedback();
            }).catch(function(err) {
                console.error('Firefox clipboard failed:', err);
                // Firefox often shows error but actually succeeds, so show success anyway
                showSuccessFeedback();
            });
        } else {
            alert('Failed to copy link to clipboard. Please copy manually: ' + shareableLink);
        }
        return;
    }

    // For other browsers, try modern clipboard API first, then fallback
    if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(shareableLink).then(function() {
            showSuccessFeedback();
        }).catch(function(err) {
            console.log('Modern clipboard failed, trying fallback method:', err);
            // Try fallback method
            if (fallbackCopyToClipboard(shareableLink)) {
                showSuccessFeedback();
            } else {
                console.error('All clipboard methods failed:', err);
                alert('Failed to copy link to clipboard. Please copy manually: ' + shareableLink);
            }
        });
    } else {
        // Use fallback method directly
        if (fallbackCopyToClipboard(shareableLink)) {
            showSuccessFeedback();
        } else {
            alert('Failed to copy link to clipboard. Please copy manually: ' + shareableLink);
        }
    }
}

/* Start new conversation */
function NewConversation() {    
    if (currentController) {
        currentController.abort();
        currentController = null;
    }
    // Remove thread_id from localStorage
    localStorage.removeItem('threadData');
    const newConversationButton = document.getElementById('new-conversation-btn');
    const suggestionPopup = document.getElementById('suggestionPopup');
    const loading = document.getElementById('loadingIndicator');
    const userInputElement = document.getElementById('user-input');
    const sendButton = document.getElementById('send-button');
    const sendIcon = document.getElementById('send-icon');
    const loadingIcon = document.getElementById('loading-icon');
    const stopIcon = document.getElementById('stop-icon');
    
    // Reset button states
    if (sendIcon) sendIcon.style.display = 'block';
    if (loadingIcon) loadingIcon.style.display = 'none';
    if (stopIcon) stopIcon.style.display = 'none';
    sendButton.classList.remove('active');
    
    // **Reset thread_id**
    threadId = null;
    sharedThreadId = null
    // **Remove thread_id parameter from the URL**
    const url = new URL(window.location);
    url.searchParams.delete('thread_id');
    window.history.replaceState({}, document.title, url.pathname + url.search);
    userInputElement.disabled = false;
    sendButton.disabled = false;
    isPaused=true;
    // **Hide the share button**
    hideShareButton()
    document.getElementById('chat-body').innerHTML = '<div class="welcome-screen" id="suggestionPopup">'+suggestionPopup.innerHTML+'</div>';
    
    // Ensure welcome screen scrolls to top to show logo
    setTimeout(() => {
        const chatBody = document.getElementById('chat-body');
        if (chatBody) {
            chatBody.scrollTop = 0;
        }
    }, 10);
    
    if (loading) {
        loading.style.display='none';
        document.getElementById('chat-body').append(loading);
    }
    if (newConversationButton) {
        newConversationButton.style.display = 'none';
    }

    // Update button layout after hiding new conversation button
    updateActionButtonsLayout();
    resetUIState();
}

// Function to create a new thread
async function createNewThread() {
  const r = await fetch(
    "https://yivtrivakoskj4a7grxr37m6n40kubhr.lambda-url.eu-west-3.on.aws/",
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        action: "createThread",
        model,
        assistantId,
        vectorstore,
        product: chat_project
      })
    }
  );
  const d = await r.json();
  if (!r.ok) throw new Error(d.error || "createThread failed");
  return JSON.parse(d.body).thread_id;
}

// Function to handle message rating
function rateMessage(messageContainer, rating) {
    const ratingContainer = messageContainer.querySelector('.rating-buttons');
    
    // Check if ratingContainer exists
    if (!ratingContainer) {
        console.error('Rating container not found');
        return;
    }
    
    const feedbackText = ratingContainer.querySelector('.rating-feedback');
    const ratingButtons = ratingContainer.querySelectorAll('.rating-btn');
    
    // Check if required elements exist
    if (!feedbackText || !ratingButtons.length) {
        console.error('Rating elements not found');
        return;
    }
    
    // Remove active class from all buttons
    ratingButtons.forEach(btn => btn.classList.remove('active'));
    
    // Add active class to the clicked button
    const clickedButton = ratingContainer.querySelector(`.rating-btn.${rating}`);
    if (clickedButton) {
        clickedButton.classList.add('active');
    }
    
    // Mark container as rated and show feedback
    ratingContainer.classList.add('rated');
    const ratingText = rating === 'thumbs-up' ? 'Good Response' : 'Bad Response';
    feedbackText.textContent = ratingText;
    
    // Log for debugging
    console.log(`Message rated: ${rating}`);
    
    // Submit rating to backend
    submitRating(messageContainer, rating);
}

/* Submit rating to backend */
async function submitRating(messageContainer, rating) {
  const messageId = messageContainer.dataset.messageId;
  if (!messageId) {
    console.error('No messageId on this bubble – cannot rate');
    return;
  }
  const ratingValue = rating === 'thumbs-up' ? 1 : 0;

  try {
    const res = await fetch("https://yivtrivakoskj4a7grxr37m6n40kubhr.lambda-url.eu-west-3.on.aws/", {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        action    : 'rateMessage',
        messageId : messageId,
        threadId  : threadId,
        rating    : ratingValue
      })
    });
    const data = await res.json();
    if (data.status !== 'saved') {
      console.error('Failed to save rating:', data);
    }
  } catch (err) {
    console.error('Error submitting rating:', err);
  }
}

// Call this function after the first user question is submitted and displayed
function showNewConversationButton() {
    const newConversationButton = document.getElementById('new-conversation-btn');
    if (newConversationButton) {
        newConversationButton.style.display = 'block';
    }
    updateActionButtonsLayout();
}

function handleKeyPress(event) {
    if (event.key === 'Enter') {
        sendMessage();
    }
}

/* Toggle chat visibility */
function toggleChat() {
    const chatContainer = document.getElementById('chat-container');
    const isVisible = chatContainer.style.display === 'flex' && chatContainer.classList.contains('show');
    
    if (!isVisible) {
        // Show the chat
        chatContainer.style.display = 'flex';
        // Add smooth animation class
        setTimeout(() => {
            chatContainer.classList.add('show');
        }, 10);
        
        // Check if there's an active conversation and update button states
        const storedThreadData = localStorage.getItem('threadData');
        let hasActiveConversation = false;
        
        if (storedThreadData) {
            const threadData = JSON.parse(storedThreadData);
            const currentTime = new Date().getTime();
            const timeDifference = currentTime - threadData.timestamp;
            
            if (timeDifference < 15 * 60 * 1000) {
                // There's an active conversation, show appropriate buttons
                hasActiveConversation = true;
                showNewConversationButton();
                showShareButton();
            }
        }
        
        // Restore saved window size if available (handled by resize script in HTML)
        // The window size restoration is already handled by the resize functionality in the HTML
        
        // Scroll chat body to top only if there's no active conversation
        setTimeout(() => {
            const chatBody = document.getElementById('chat-body');
            if (chatBody) {
                // Only scroll to top if there are no messages (showing welcome screen)
                const messages = chatBody.querySelectorAll('.message.user, .message.assistant:not(.loading-message)');
                if (messages.length === 0) {
                    chatBody.scrollTop = 0;
                } 
            }
        }, 50);
        
        // Focus on input field
        setTimeout(() => {
            const userInput = document.getElementById('user-input');
            if (userInput && !userInput.disabled) {
                userInput.focus();
            }
        }, 350);
    } else {
        // Hide the chat
        chatContainer.classList.remove('show');
        
        // Check if there's an active conversation and show continuation notification
        const storedThreadData = localStorage.getItem('threadData');
        if (storedThreadData) {
            const threadData = JSON.parse(storedThreadData);
            const currentTime = new Date().getTime();
            const timeDifference = currentTime - threadData.timestamp;
            
            if (timeDifference < 15 * 60 * 1000) {
                // Show continuation notification when closing chat with active conversation
                if (typeof window.showConversationNotification === 'function') {
                    // Use a delay to ensure the chat close animation starts first
                    setTimeout(() => {
                        window.showConversationNotification();
                    }, 200);
                }
            }
        }
        
        // Hide after animation completes
        setTimeout(() => {
            chatContainer.style.display = 'none';
        }, 300);
    }
}

function showShareButton() {
    const shareButton = document.getElementById('share-conversation-button');
    if (shareButton) {
        shareButton.style.display = 'inline-flex';
    }
    updateActionButtonsLayout();
}

function hideShareButton() {
    const shareButton = document.getElementById('share-conversation-button');
    if (shareButton) {
        shareButton.style.display = 'none';
    }
    updateActionButtonsLayout();
}

// Function to update the action buttons grid layout based on visible buttons
function updateActionButtonsLayout() {
    const grid = document.querySelector('.action-buttons-grid');
    if (!grid) return;
    
    // Count visible buttons
    const buttons = grid.querySelectorAll('button');
    let visibleCount = 0;
    
    buttons.forEach(button => {
        const computedStyle = window.getComputedStyle(button);
        if (computedStyle.display !== 'none') {
            visibleCount++;
        }
    });
    
  // If no buttons are visible, hide the grid entirely so the textarea can use the space
  if (visibleCount === 0) {
    grid.style.display = 'none';
  } else {
    grid.style.display = 'grid';
  }

    // Remove existing layout classes
    grid.classList.remove('layout-1', 'layout-2', 'layout-3');
    
    // Apply appropriate layout class
    switch(visibleCount) {
        case 1:
            grid.classList.add('layout-1');
            break;
        case 2:
            grid.classList.add('layout-2');
            break;
        case 3:
        default:
            grid.classList.add('layout-3');
            break;
    }
}

function startTypingAnimation() {
    const chatInput = document.querySelector('.chat-input');
    if (chatInput) {
        chatInput.classList.add('typing');
    }
}

function stopTypingAnimation() {
    const chatInput = document.querySelector('.chat-input');
    if (chatInput) {
        chatInput.classList.remove('typing');
    }
}

/* -------------------------------------------------------------
 *  PROJECT CONFIGURATION MAPPING
 * ----------------------------------------------------------- */
// Define the assistantId and vectorstore mappings based on the project
const configMap = {
    "Clio Documentation": {
        assistantId: "asst_a1RgZSeFSBw6wwPzN2pbyWtT",
        vectorstore: {
            staging:"vs_Ea6fBXVL4OHKxtyx4cFG8Byn",
            production:"vs_ui9SNyMWN1H2qmQJukGpsSju"
        }
    },
    "HOOPS Exchange": {
        assistantId: "asst_a1RgZSeFSBw6wwPzN2pbyWtT",
        vectorstore: {
            production:"vs_Ea6fBXVL4OHKxtyx4cFG8Byn",
            staging:"vs_ui9SNyMWN1H2qmQJukGpsSju"
        }
    },
    "HOOPS Publish": {
        assistantId: "asst_YppAVaiTHIGFtHh60jDDD5av",
        vectorstore: {
            production:"vs_3eTt3TPZ29dDXsWuh6bDn62t",
            staging:"vs_mmV0fxQBi7pA6dc8c5aZLNiU"
        }
    },
    "HOOPS Visualize Web": {
        assistantId: "asst_1MkTnjU8WzzztERYpuY4YG3U",
        vectorstore: {
            staging:"vs_MbxNBeiQjD8OPUa6SZzm8uqp",
            production:"vs_kVvElCt8DWRJT6gUgcIPcuO6"
        }
    },
    "HOOPS Visualize Desktop": {
        assistantId: "asst_32Ay3Ut2cGuKHEBhdgDQJTII",
        vectorstore: {
            staging:"vs_JqcLrdtA0jwTrkuty105MEwa",
            production:"vs_Y7UMCLqqZPoudrvEBocdB1LM"
        }
    },
    "HOOPS Visualize 3DF": {
        assistantId: "asst_Bzcm3jSDCBJCcggtSArRTMza",
        vectorstore: {
            staging:"vs_MQUfmP3rq6yBVSoiUNzg2OSM",
            production:"vs_jOlieuQzZrgadbgMOsTrERvk"
        }
    },
    "HOOPS Luminate": {
        assistantId: "asst_PshlEOWECvGwNF6h26tFgEo6",
        vectorstore: {
            staging:"vs_Lzo6rCLZVZOgeh8DPpbYvOh0",
            production:"vs_Na3NFPxbTPEDjemyz7VG0ibn"
        }
    },   
    "HOOPS Envision for Desktop": {
        assistantId: "asst_D614GOxnxQnBQXGiga5ixI3d",
        vectorstore: {
            staging:"vs_2mAlf8IJ2shp0qOtfK2BZx2f",
            production:"vs_Wr9NMvU5H9XZ5QHsTB8UGUtE"
        }
    },
    "HOOPS Envision for Web": {
        assistantId: "asst_PshlEOWECvGwNF6h26tFgEo6",
        vectorstore: {
            staging:"vs_TFDVdHAsbWUaPBwc5UtYxpVO",
            production:"vs_hkGjAvi1E3s2gB6jdZ0nlU4d"
        }
    },
    "HOOPS Solve": {
        assistantId: "asst_iYHRJ5kjfJGlFQniuJC6ieAK",
        vectorstore: {
            staging:"vs_Kuvy2wwwhLSQYs67HIjnQCSs",
            production:"vs_XjDZDMfnr38yMXhSolJxay9i"
        }
    },
    "HOOPS Mesh":{
        assistantId: "asst_gzYcSf2UOqtPUOLRIUaWvfpY",
        vectorstore: {
            staging:"vs_68a5dcfcc4648191833385e2773e3dc1",
            production:"vs_68a5dd109fd88191bdb5a733df66560a"
        }
    },
    "HOOPS Access":{
        assistantId: "asst_fFfKUDEXTbsqxSgijMtssjQ9",
        vectorstore: {
            staging:"vs_68a5dccfc25881918e155cbc78067606",
            production:"vs_68a5dcefea708191bc7ff2d8be9ee659"
        }
    },
    "SpinFire Insight": {
        assistantId: "asst_RJQOr6AJ25ie1BEOPzWvgAIW",
        vectorstore: {
            staging:"vs_6888e5145d1881918e2996f417464075",
            production:"vs_6888e06f57fc8191bc10101ff8a25776"
        }
    },
    "SpinFire Publish": {
        assistantId: "asst_vJkTsDnjv0Hawc89ql2atvBp",
        vectorstore: {
            staging:"vs_6891d10cd044819191a75eb5da429f7c",
            production:"vs_6891d201619c819196c4f4a201d6e6ed"
        }
    },
    "SpinFire Manage": {
        assistantId: "asst_WYillFBlyX6KNFPt5rLBZqic",
        vectorstore: {
            staging:"vs_689203d645948191b66b1bed400a3bd6",
            production:"vs_68921078277881918c3282dd49b6f327"
        }
    },
    "SpinFire XR": {
        assistantId: "asst_i54K8o7WhCqm2fOeRTtDz6nU",
        vectorstore: {
            staging:"vs_689204f00bdc8191bbfbbf557778923d",
            production:"vs_68d53d272ed8819194ebb5719f894732"
        }
    },
    "SpinFire Convert": {
        assistantId: "asst_wiEDyOojgdqhZJ9qBqOaNqmj",
        vectorstore: {
            staging:"vs_68a31a7be8e881918bfb6bb99f793e56",
            production:"vs_68a31af54de48191832051487b723480"
        }
    },
    "HOOPS AI" : {
        assistantId: "asst_uD8dW2N6zMxljdhyz8LB4fLw",
        vectorstore: {
            staging:"vs_69f0c6a94bd88191aecde030fe23a32b",
            production:"vs_690b1571aed08191aa90d21664a083a1"
        }
    },
    "Tetra4D Enrich": {
        assistantId: "asst_COTSrfi4pqxocOXrgemRHtdC",
        vectorstore: {
            staging:"vs_696f41e1cf94819198136d981a0bf8c5", 
            production:"vs_698464e3cc14819190b84c64aed78adf"
        }
    },
    "Tetra4D Automate": {
        assistantId: "asst_2fCswfwIQHZZbfqQ7FzVhj5m",
        vectorstore: {
            staging:"vs_696f4147c36c81918ba15e18b84ec29d", 
            production:"vs_6984654135808191b1c2418401a85731"
        }
    },
    "Tetra4D Converter": {
        assistantId: "asst_fPsTzIAyfiJqW6QF5ipWgd1J",
        vectorstore: {
            staging:"vs_696f407dbac0819196a9c978c24e475d", 
            production:"vs_6984655396208191bb75cc610df2c3fb"
        }
    }
};

/* -------------------------------------------------------------
 *  REFERENCE URL UTILITIES 
 * ----------------------------------------------------------- */
/* Fetch HTML title from URL for reference links */
async function fetchHtmlTitle(url) {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const text = await response.text();
        const parser = new DOMParser();
        const doc = parser.parseFromString(text, 'text/html');
        const title = doc.querySelector('title')?.innerText || 'Documentation Link';
        return title;
    } catch (error) {
        console.error('Error fetching HTML title:', error);
        // Extract the page name from the URL as a fallback
        const pageName = url.split('/').pop().replace('.html', '').replace(/-/g, ' ');
        return pageName ? `${pageName}` : 'Documentation Link';
    }
}

// Keep the vsConfigMap as it contains the mapping from vs_id to specific product documentation URLs
const vsConfigMap = {
  "vs_698464e3cc14819190b84c64aed78adf": {
        product: "Tetra4D Enrich",
        productionDocUrl: "https://docs.techsoft3d.com/tetra4d/enrich/",
        testingDocUrl: "https://docs-test.techsoft3d.com/tetra4d/enrich/main/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/tetra4d/enrich/"
  },
  "vs_696f41e1cf94819198136d981a0bf8c5": {
        product: "Tetra4D Enrich",
        productionDocUrl: "https://docs.techsoft3d.com/tetra4d/enrich/",
        testingDocUrl: "https://docs-test.techsoft3d.com/tetra4d/enrich/main/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/tetra4d/enrich/"
  },
  "vs_6984654135808191b1c2418401a85731": {
        product: "Tetra4D Automate",
        productionDocUrl: "https://docs.techsoft3d.com/tetra4d/automate/",
        testingDocUrl: "https://docs-test.techsoft3d.com/tetra4d/automate/main/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/tetra4d/automate/"
  },
  "vs_696f4147c36c81918ba15e18b84ec29d": {
        product: "Tetra4D Automate",
        productionDocUrl: "https://docs.techsoft3d.com/tetra4d/automate/",
        testingDocUrl: "https://docs-test.techsoft3d.com/tetra4d/automate/main/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/tetra4d/automate/"
  },
  "vs_6984655396208191bb75cc610df2c3fb": {
        product: "Tetra4D Converter",
        productionDocUrl: "https://docs.techsoft3d.com/tetra4d/converter/",
        testingDocUrl: "https://docs-test.techsoft3d.com/tetra4d/converter/main/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/tetra4d/converter/"
  },
  "vs_696f407dbac0819196a9c978c24e475d": {
        product: "Tetra4D Converter",
        productionDocUrl: "https://docs.techsoft3d.com/tetra4d/converter/",
        testingDocUrl: "https://docs-test.techsoft3d.com/tetra4d/converter/main/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/tetra4d/converter/"
  },
  "vs_68a31a7be8e881918bfb6bb99f793e56": {
        product: "SpinFire Convert",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/convert/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/convert/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/convert/"
  },
  "vs_68a31af54de48191832051487b723480": {
        product: "SpinFire Convert",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/convert/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/convert/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/convert/"
  },
  "vs_68d53d272ed8819194ebb5719f894732": {
        product: "SpinFire XR",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/xr/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/xr/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/xr/"
  },
  "vs_689204f00bdc8191bbfbbf557778923d":{
        product: "SpinFire XR",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/xr/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/xr/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/xr/"
  },
  "vs_689203d645948191b66b1bed400a3bd6": {
        product: "SpinFire Manage",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/manage/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/manage/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/manage/"
  },
  "vs_68921078277881918c3282dd49b6f327": {
        product: "SpinFire Manage",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/manage/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/manage/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/manage/"
  },
  "vs_6891d10cd044819191a75eb5da429f7c": {
        product: "SpinFire Publish",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/publish/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/publish/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/publish/"
    },
  "vs_6891d201619c819196c4f4a201d6e6ed": {
        product: "SpinFire Publish",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/publish/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/publish/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/publish/"
    },
  "vs_6888e5145d1881918e2996f417464075": {
        product: "SpinFire Insight",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/insight/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/insight/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/insight/"
    },
  "vs_6888e06f57fc8191bc10101ff8a25776": {
        product: "SpinFire Insight",
        productionDocUrl: "https://docs.techsoft3d.com/spinfire/insight/",
        testingDocUrl: "https://docs-test.techsoft3d.com/spinfire/insight/master/",
        stagingDocUrl: "https://staging.docs.techsoft3d.com/spinfire/insight/"
    },
  "vs_Ea6fBXVL4OHKxtyx4cFG8Byn": {
    product: "HOOPS Exchange",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/exchange/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/exchange/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/exchange/"
  },
  "vs_ui9SNyMWN1H2qmQJukGpsSju": {
    product: "HOOPS Exchange",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/exchange/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/exchange/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/exchange/"
  },
  "vs_3eTt3TPZ29dDXsWuh6bDn62t": {
    product: "HOOPS Publish",
    productionDocUrl: "https://docs.techsoft3d.com/publish/",
    testingDocUrl: "https://docs-test.techsoft3d.com/publish/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/exchange/"
  },
  "vs_mmV0fxQBi7pA6dc8c5aZLNiU": {
    product: "HOOPS Publish",
    productionDocUrl: "https://docs.techsoft3d.com/publish/",
    testingDocUrl: "https://docs-test.techsoft3d.com/publish/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/exchange/"
  },
  "vs_MbxNBeiQjD8OPUa6SZzm8uqp": {
    product: "HOOPS Visualize Web",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/visualize-web/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/visualize-web/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/visualize-web/"
  },
  "vs_kVvElCt8DWRJT6gUgcIPcuO6": {
    product: "HOOPS Visualize Web",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/visualize-web/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/visualize-web/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/visualize-web/"
  },
  "vs_JqcLrdtA0jwTrkuty105MEwa": {
    product: "HOOPS Visualize Desktop",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/visualize-desktop/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/visualize-desktop/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/visualize-desktop/"
  },
  "vs_Y7UMCLqqZPoudrvEBocdB1LM": {
    product: "HOOPS Visualize Desktop",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/visualize-desktop/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/visualize-desktop/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/visualize-desktop/"
  },
  "vs_MQUfmP3rq6yBVSoiUNzg2OSM": {
    product: "HOOPS Visualize 3DF",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/visualize-3df/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/visualize-3df/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/visualize-3df/"
  },
  "vs_jOlieuQzZrgadbgMOsTrERvk": {
    product: "HOOPS Visualize 3DF",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/visualize-3df/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/visualize-3df/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/visualize-3df/"
  },
  "vs_Lzo6rCLZVZOgeh8DPpbYvOh0": {
    product: "HOOPS Luminate",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/luminate/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/luminate/master/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/luminate/"
  },
  "vs_Na3NFPxbTPEDjemyz7VG0ibn": {
    product: "HOOPS Luminate",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/luminate/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/luminate/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/luminate/"
  },
  "vs_2mAlf8IJ2shp0qOtfK2BZx2f": {
    product: "HOOPS Envision for Desktop",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/envision-desktop/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/envision-desktop/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/envision-desktop/"
  },
  "vs_Wr9NMvU5H9XZ5QHsTB8UGUtE": {
    product: "HOOPS Envision for Desktop",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/envision-desktop/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/envision-desktop/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/envision-desktop/"
  },
  "vs_TFDVdHAsbWUaPBwc5UtYxpVO": {
    product: "HOOPS Envision for Web",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/envision-web/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/envision-web/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/envision-web/"
  },
  "vs_hkGjAvi1E3s2gB6jdZ0nlU4d": {
    product: "HOOPS Envision for Web",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/envision-web/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/envision-web/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/envision-web/"
  },
  "vs_Kuvy2wwwhLSQYs67HIjnQCSs": {
    product: "HOOPS Solve",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/solve/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/solve/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/solve/"
  },
  "vs_XjDZDMfnr38yMXhSolJxay9i": {
    product: "HOOPS Solve",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/solve/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/solve/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/solve/"
  },
  "vs_68a5dcfcc4648191833385e2773e3dc1": {
    product: "HOOPS Mesh",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/mesh/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/mesh/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/mesh/"
  },
  "vs_68a5dd109fd88191bdb5a733df66560a":{
    product: "HOOPS Mesh",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/mesh/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/mesh/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/mesh/"
  },
  "vs_68a5dccfc25881918e155cbc78067606": {
    product: "HOOPS Access",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/access/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/access/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/access/"
  },
  "vs_68a5dcefea708191bc7ff2d8be9ee659":{
    product: "HOOPS Access",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/access/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/access/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/access/"
  },
  "vs_69f0c6a94bd88191aecde030fe23a32b": {
    product: "HOOPS AI",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/ai/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/ai/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/ai/"
  },
  "vs_690b1571aed08191aa90d21664a083a1":{
    product: "HOOPS AI",
    productionDocUrl: "https://docs.techsoft3d.com/hoops/ai/",
    testingDocUrl: "https://docs-test.techsoft3d.com/hoops/ai/main/",
    stagingDocUrl: "https://staging.docs.techsoft3d.com/hoops/ai/"
  }
};

/* Get documentation URL based on vector store ID and environment */
function getDocUrlFromVsId(vsId) {
  const config = vsConfigMap[vsId];
  if (!config) {
    // Return the default urlRoot if vs_id is unknown
    return urlRoot;
  }
  const isProduction = window.location.hostname === "docs.techsoft3d.com";
  const isTesting = window.location.hostname === "docs-test.techsoft3d.com";
  const isStaging = window.location.hostname === "staging.docs.techsoft3d.com";
  
  if (isProduction) {
    return config.productionDocUrl;
  } else if (isTesting) {
    return config.testingDocUrl;
  } else if (isStaging) {
    return config.stagingDocUrl;
  }
  
  // Default fallback
  return config.productionDocUrl;
}

/* Handle question button click */
function handleQuestionButtonClick(button) {
    const question = button.innerText;
    sendMessage(question);
}

/* -------------------------------------------------------------
 *  Silent thread loader – NO typewriter, same UI as live chat
 * ----------------------------------------------------------- */
async function loadThread(tid) {
  if (!tid) return;
  hideIntroScreens()
  const chatBody  = qs("#chat-body");
  const spinner   = qs("#loadingIndicator");

  // Update loading text for thread loading
  updateLoadingText("Loading conversation...");
  show(spinner);

  try {
    /* 1 ▸ fetch thread */
    const res = await fetch("https://yivtrivakoskj4a7grxr37m6n40kubhr.lambda-url.eu-west-3.on.aws/", {
      method : "POST",
      headers: { "Content-Type": "application/json" },
      body   : JSON.stringify({
        action   : "getThread",
        threadId : tid,
        product  : chat_project,
        includeVsId: true
      })
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const { messages = [] } = await res.json();

    /* ensure spinner is last child so addMessage can insert before it */
    if (spinner.parentNode !== chatBody) chatBody.appendChild(spinner);

    /* 3 ▸ reset globals */
    queue = ""; isPaused = false;

    /* 4 ▸ draw from oldest ➜ newest */
    const ordered = [...messages];       // chronological
    let lastUserVsId = null;

    ordered.forEach(m => {
      /* skip empty shells */
      if (!m.content?.trim()) return;

      const role   = m.role === "user" ? "user" : "assistant";
      const bubble = addMessage(role, "");

      /* print markdown immediately (no typewriter) and clean citations */
      bubble.raw = m.content;
      bubble.txt.innerHTML = parseMarkdownSafely(bubble.raw);

      if (role === "assistant") {
        // Apply syntax highlighting and copy buttons for loaded content
        const codeBlocks = bubble.wrap.querySelectorAll('pre code');
        codeBlocks.forEach(codeElement => {
          if (window.hljs && !codeElement.dataset.highlighted) {
            hljs.highlightElement(codeElement);
            codeElement.dataset.highlighted = 'true';
          }
        });
        addCodeCopyButtons(bubble.wrap);
        
        // Finish assistant message setup
        const vsId = m.vs_id || lastUserVsId || vectorStore;
        finishAssistant(bubble, {
          references: m.references || [],
          vs_id     : vsId,
          rated     : !!m.rated,
          rating    : m.rating,
          skipMarkdownReparse: true  // Already parsed correctly at line 1836
        });
        // Preserve message id so rating can be prevented client-side
        if (m.message_id) {
          bubble.wrap.dataset.messageId = m.message_id;
        }
      } else if (m.vs_id) {
        lastUserVsId = m.vs_id;                 // remember for next assistant
      }
    });

    /* 5 ▸ state + UI */
    threadId = tid;
    localStorage.setItem(
      "threadData",
      JSON.stringify({ thread_id: threadId, timestamp: Date.now() })
    );

    showNewConversationButton();
    showShareButton();
    updateActionButtonsLayout();
    scrollToTop();
  } catch (err) {
    console.error("Silent-load failed:", err);
  } finally {
    hide(spinner);
  }
}

/* -------------------------------------------------------------
 *  INIT
 * ----------------------------------------------------------- */
document.addEventListener("DOMContentLoaded", () => {
  urlRoot = typeof Path_ROOT !== "undefined" ? Path_ROOT : qs("#documentation_options").dataset.url_root;
  ({ assistantId, vectorstore: vectorStore } = cfg(chat_project));
  const prod = location.hostname === "docs.techsoft3d.com";
  vectorStore = prod ? vectorStore.production : vectorStore.staging;
  updateActionButtonsLayout();

  qs("#send-button").onclick   = () => sendMessage();
  qs("#user-input").addEventListener("keydown", e => {
      if (e.key === "Enter" && !e.shiftKey) {   // allow Shift+Enter for newline
        e.preventDefault();                     // stop newline in the input
        sendMessage();
      }
  });
  const chatBody = document.getElementById('chat-body');
  // Attach scroll event listener to chat body
  chatBody.addEventListener('scroll', handleUserScroll);

  // Check for thread_id in query param or localStorage and load thread
  const urlParams = new URLSearchParams(window.location.search);
  let threadIdFromQuery = urlParams.get("thread_id");
  let threadIdFromStorage = null;
  const saved = JSON.parse(localStorage.getItem("threadData") || "null");
  if (saved && Date.now() - saved.timestamp < 9e5) threadIdFromStorage = saved.thread_id;

  if (threadIdFromQuery) {
    loadThread(threadIdFromQuery);
    toggleChat()
  } else if (threadIdFromStorage) {
    loadThread(threadIdFromStorage);
  }
});

// expose minimal globals
window.sendMessage = sendMessage;
window.stopTyping  = stopTyping;
