// ============================================================
// CLINICAL ASSISTANT — evidence-grounded chat UI.
// Backend contract intentionally uses /api/clinical-assistant/* so the
// server can call native MCP directly without routing through mcpo.
// ============================================================
import { EMPTY_PROMPT_SETS } from './assistant/data.js';
import { escapeAttr, escapeHtml, renderAssistantMarkdown } from './assistant/citations.js';
import { renderSourcesList } from './assistant/sources.js';
import { createAssistantExporter } from './assistant/export.js';
import { buildContextualImagePrompt, createAssistantImageStore, isImageRequest } from './assistant/images.js';
import {
  deleteSavedAssistantChat,
  fetchAssistantChat,
  fetchAssistantExamples,
  fetchAssistantStatus,
  fetchSavedAssistantChat,
  fetchSavedAssistantChats,
  openAssistantStream,
  fetchAssistantImageJob,
  startAssistantImageJob,
  saveAssistantChat
} from './assistant/api.js';
  var initialized = false;
  var messages = [];
  var lastAnswer = '';
  var lastSources = [];
  var mermaidReady = false;
  var dynamicExamples = [];
  var lastGeneratedImageSrc = '';
  var markdownRenderer = null;
  var assistantBusy = false;
  var activeAssistantRequest = null;
  var STREAM_MARKDOWN_LIMIT = 3500;
  var exporter = createAssistantExporter({ renderMarkdown: renderMarkdown, showToast: window.showToast });
  var imageStore = createAssistantImageStore();

  document.addEventListener('tabChanged', function (e) {
    if (e.detail && e.detail.tab === 'assistant') initIfNeeded();
  });

  function initIfNeeded() {
    if (initialized) return;
    var root = document.getElementById('assistant-tab');
    if (!root || !root.querySelector('#assistant-form')) return;
    initialized = true;
    bindEvents();
    loadStatus();
    loadExamples();
  }

  function bindEvents() {
    var form = document.getElementById('assistant-form');
    var clearBtn = document.getElementById('btn-assistant-clear');
    var cancelBtn = document.getElementById('btn-assistant-cancel');
    var copyBtn = document.getElementById('btn-assistant-copy');
    var saveBtn = document.getElementById('btn-assistant-save');
    var saveConfirmBtn = document.getElementById('btn-assistant-save-confirm');
    var saveCancelBtn = document.getElementById('btn-assistant-save-cancel');
    var exportBtn = document.getElementById('btn-assistant-export-pdf');
    var imageBtn = document.getElementById('btn-assistant-image');
    var imageClearBtn = document.getElementById('btn-assistant-image-clear');
    var input = document.getElementById('assistant-input');

    if (form) form.addEventListener('submit', onAsk);
    if (clearBtn) clearBtn.addEventListener('click', clearConversation);
    if (cancelBtn) cancelBtn.addEventListener('click', cancelAssistantSearch);
    if (copyBtn) copyBtn.addEventListener('click', copyLastAnswer);
    if (saveBtn) saveBtn.addEventListener('click', showSavePanel);
    if (saveConfirmBtn) saveConfirmBtn.addEventListener('click', saveCurrentChat);
    if (saveCancelBtn) saveCancelBtn.addEventListener('click', hideSavePanel);
    if (exportBtn) exportBtn.addEventListener('click', exportAnswerPdf);
    if (imageBtn) imageBtn.addEventListener('click', generateImage);
    if (imageClearBtn) imageClearBtn.addEventListener('click', clearGeneratedImage);
    document.addEventListener('click', onAssistantDocumentClick);
    if (input) input.addEventListener('keydown', function (e) {
      if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') onAsk(e);
    });

    bindExampleButtons(document);
    loadSavedChats();
  }

  function loadStatus() {
    fetchAssistantStatus()
      .then(function (data) {
        var label = document.getElementById('assistant-model-label');
        if (label && data.success) {
          label.textContent = data.chatModel ? ('Chat: ' + data.chatModel) : 'Admin model';
        }
      })
      .catch(function () {
        // Endpoint may not exist until backend slice lands; keep UI usable.
      });
  }

  function loadExamples() {
    fetchAssistantExamples()
      .then(function (data) {
        if (!data.success || !Array.isArray(data.examples) || !data.examples.length) return;
        dynamicExamples = data.examples;
        var wrap = document.getElementById('assistant-messages');
        if (wrap && wrap.querySelector('.assistant-empty')) {
          wrap.innerHTML = renderEmptyState();
          bindExampleButtons(wrap);
        }
      })
      .catch(function () {});
  }

  function onAsk(e) {
    if (e) e.preventDefault();
    var input = document.getElementById('assistant-input');
    var includeContext = document.getElementById('assistant-include-context');
    var text = input ? input.value.trim() : '';
    if (assistantBusy) {
      if (typeof showToast === 'function') showToast('Assistant is still finishing the current answer', 'error');
      return;
    }
    if (!text) {
      if (typeof showToast === 'function') showToast('Enter a clinical question', 'error');
      return;
    }

    appendMessage('user', text);
    exporter.invalidate();
    if (input) input.value = '';

    if (isImageRequest(text)) {
      prepareSidebarImagePrompt(text);
      return;
    }

    var request = createAssistantRequest();
    activeAssistantRequest = request;
    setBusy(true, 'Looking up sources...');
    var loading = appendLoadingMessage('Looking up sources', 'Retrieving and synthesizing references...');
    request.loading = loading;

    streamAssistantResponse({
      message: text,
      history: messages.slice(-8),
      includeContext: !includeContext || includeContext.checked
    }, loading, request)
    .catch(function (err) {
      if (request.cancelled) return;
      setBusy(false, 'Error', true);
      replaceLoadingMessage(loading, 'I could not complete the assistant request. ' + err.message + '\n\nThe indexed search service may be busy or temporarily unavailable. Please try again in a moment.');
      if (typeof showToast === 'function') showToast(err.message, 'error');
    })
    .finally(function () {
      if (activeAssistantRequest === request) activeAssistantRequest = null;
    });
  }

  function createAssistantRequest() {
    var controller = typeof AbortController === 'function' ? new AbortController() : null;
    return {
      cancelled: false,
      signal: controller ? controller.signal : undefined,
      abort: function () {
        this.cancelled = true;
        if (controller) controller.abort();
      }
    };
  }

  async function fetchAssistantResponse(payload, loading) {
    updateLoadingMessage(loading, 'Looking up sources...');
    var data = await fetchAssistantFallback(payload);
    setBusy(false, 'Ready');
    lastAnswer = data.answer || '';
    lastSources = data.sources || [];
    replaceLoadingMessage(loading, lastAnswer, lastSources, data.suggestions || []);
    renderSources(lastSources);
    if (data.model) {
      var label = document.getElementById('assistant-model-label');
      if (label) label.textContent = 'Chat: ' + data.model;
    }
  }

  async function streamAssistantResponse(payload, loading, request) {
    var response = await openAssistantStream(payload, { signal: request ? request.signal : undefined });
    if (!response.ok || !response.body) {
      var fallback = await response.json().catch(function () { return {}; });
      throw new Error(fallback.error || ('Request failed (' + response.status + ')'));
    }

    var partial = '';
    var streamSources = [];
    var finalData = null;
    var lastRender = 0;
    var bubble = loading ? loading.querySelector('.assistant-bubble') : null;
    var decoder = new TextDecoder();
    var buffer = '';

    function renderProvisional(force) {
      var now = Date.now();
      if (!force && now - lastRender < 180) return;
      lastRender = now;
      if (!bubble) return;
      loading.classList.remove('assistant-loading-msg');
      bubble.classList.remove('assistant-thinking');
      bubble.innerHTML = partial ? renderStreamingAnswerHtml(partial, streamSources) : '<p class="assistant-muted">Generating answer...</p>';
      renderEmbeddedBlocks(bubble);
      var wrap = document.getElementById('assistant-messages');
      if (wrap) wrap.scrollTop = wrap.scrollHeight;
    }

    function handleEvent(type, data) {
      if (type === 'status') {
        updateLoadingMessage(loading, data.message || 'Working...');
        return;
      }
      if (type === 'sources') {
        streamSources = data.sources || [];
        renderSources(streamSources);
        return;
      }
      if (type === 'token') {
        partial += data.token || '';
        renderProvisional(false);
        return;
      }
      if (type === 'done') {
        finalData = data || {};
        return;
      }
      if (type === 'error') throw new Error(data.error || 'Assistant stream failed');
    }

    var reader = response.body.getReader();
    while (true) {
      if (request && request.cancelled) return;
      var chunk = await reader.read();
      if (chunk.done) break;
      buffer += decoder.decode(chunk.value, { stream: true });
      var parts = buffer.split('\n\n');
      buffer = parts.pop() || '';
      parts.forEach(function (part) {
        var parsed = parseSseEvent(part);
        if (parsed) handleEvent(parsed.type, parsed.data);
      });
    }
    if (buffer.trim()) {
      var tail = parseSseEvent(buffer);
      if (tail) handleEvent(tail.type, tail.data);
    }

    if (!finalData) {
      updateLoadingMessage(loading, 'Stream ended early. Retrying without streaming...');
      finalData = await fetchAssistantFallback(payload, request);
    }

    if (request && request.cancelled) return;

    setBusy(false, 'Ready');
    lastAnswer = finalData.answer || finalData.markdown || '';
    lastSources = finalData.sources || finalData.citations || streamSources;
    replaceLoadingMessage(loading, lastAnswer, lastSources, finalData.suggestions || []);
    renderSources(lastSources);
    if (finalData.model) {
      var label = document.getElementById('assistant-model-label');
      if (label) label.textContent = 'Chat: ' + finalData.model;
    }
  }

  function renderStreamingAnswerHtml(text, sources) {
    if (shouldUseLightweightStreamingRender(text)) {
      return '<pre class="assistant-streaming-text">' + escapeHtml(text) + '</pre>';
    }
    return renderAssistantBubbleHtml(text, sources, false);
  }

  function shouldUseLightweightStreamingRender(text) {
    text = String(text || '');
    if (text.length > STREAM_MARKDOWN_LIMIT) return true;
    var pipeRows = text.split('\n').filter(function (line) { return /^\s*\|.*\|\s*$/.test(line); }).length;
    return pipeRows >= 8;
  }

  function parseSseEvent(block) {
    var type = 'message';
    var data = '';
    String(block || '').split(/\r?\n/).forEach(function (line) {
      if (line.indexOf('event:') === 0) type = line.substring(6).trim();
      if (line.indexOf('data:') === 0) data += line.substring(5).trim();
    });
    if (!data) return null;
    try { return { type: type, data: JSON.parse(data) }; }
    catch (e) { return null; }
  }

  async function fetchAssistantFallback(payload, request) {
    var data;
    try {
      data = await fetchAssistantChat(payload, { signal: request ? request.signal : undefined });
    } catch (e) {
      if (request && request.cancelled) throw e;
      if (e && e.name === 'AbortError') throw new Error('Assistant request cancelled.');
      throw e;
    }
    if (!data.success) throw new Error(data.error || ('Request failed (' + data._status + ')'));
    return data;
  }

  function updateLoadingMessage(row, detail) {
    if (!row) return;
    var el = row.querySelector('.assistant-thinking-detail');
    if (el) el.textContent = detail;
  }

  function appendLoadingMessage(title, detail) {
    var wrap = document.getElementById('assistant-messages');
    if (!wrap) return null;
    var empty = wrap.querySelector('.assistant-empty');
    if (empty) empty.remove();
    var row = document.createElement('div');
    row.className = 'assistant-msg assistant assistant-loading-msg';
    var label = document.createElement('div');
    label.className = 'assistant-msg-label';
    label.textContent = 'Assistant';
    var bubble = document.createElement('div');
    bubble.className = 'assistant-bubble assistant-thinking';
    bubble.innerHTML = '<div class="assistant-thinking-line"><span class="assistant-thinking-dot"></span><span class="assistant-thinking-dot"></span><span class="assistant-thinking-dot"></span><strong>' + escapeHtml(title || 'Working') + '</strong></div>' +
      '<div class="assistant-thinking-detail">' + escapeHtml(detail || 'Preparing response...') + '</div>';
    row.appendChild(label);
    row.appendChild(bubble);
    wrap.appendChild(row);
    wrap.scrollTop = wrap.scrollHeight;
    return row;
  }

  function replaceLoadingMessage(row, content, sources, suggestions, rawHtml) {
    if (!row || !row.parentNode) {
      appendMessage('assistant', content, sources, suggestions, rawHtml);
      return;
    }
    var bubble = row.querySelector('.assistant-bubble');
    if (!bubble) return;
    row.classList.remove('assistant-loading-msg');
    bubble.classList.remove('assistant-thinking');
    bubble.innerHTML = renderAssistantBubbleHtml(content, sources || [], rawHtml);
    if (suggestions && suggestions.length) bubble.appendChild(renderSuggestionButtons(suggestions));
    renderEmbeddedBlocks(bubble);
    var wrap = document.getElementById('assistant-messages');
    if (wrap) wrap.scrollTop = wrap.scrollHeight;
    messages.push({ role: 'assistant', content: content, sources: sources || [] });
  }

  function renderAssistantBubbleHtml(content, sources, rawHtml) {
    if (rawHtml) return sanitize(String(content || ''));
    try {
      return renderMarkdown(content, sources || []);
    } catch (e) {
      console.warn('[clinical-assistant] markdown render failed:', e && e.message ? e.message : e);
      return '<p>' + escapeHtml(String(content || '')).replace(/\n/g, '<br>') + '</p>';
    }
  }

  function appendMessage(role, content, sources, suggestions, rawHtml) {
    var wrap = document.getElementById('assistant-messages');
    if (!wrap) return;
    var empty = wrap.querySelector('.assistant-empty');
    if (empty) empty.remove();

    messages.push({ role: role, content: content, sources: role === 'assistant' ? (sources || []) : [] });
    var row = document.createElement('div');
    row.className = 'assistant-msg ' + role;
    var label = document.createElement('div');
    label.className = 'assistant-msg-label';
    label.textContent = role === 'user' ? 'You' : 'Assistant';
    var bubble = document.createElement('div');
    bubble.className = 'assistant-bubble';
    bubble.innerHTML = rawHtml ? sanitize(String(content || '')) : (role === 'assistant' ? renderMarkdown(content, sources || []) : escapeHtml(content));
    if (role === 'assistant' && suggestions && suggestions.length) {
      bubble.appendChild(renderSuggestionButtons(suggestions));
    }
    row.appendChild(label);
    row.appendChild(bubble);
    wrap.appendChild(row);
    renderEmbeddedBlocks(bubble);
    wrap.scrollTop = wrap.scrollHeight;
  }

  function renderSuggestionButtons(suggestions) {
    var wrap = document.createElement('div');
    wrap.className = 'assistant-suggestion-buttons';
    suggestions.forEach(function (s) {
      var btn = document.createElement('button');
      btn.type = 'button';
      btn.textContent = s;
      btn.addEventListener('click', function () {
        var input = document.getElementById('assistant-input');
        if (input) input.value = s;
        onAsk(new Event('submit'));
      });
      wrap.appendChild(btn);
    });
    return wrap;
  }

  function renderMarkdown(md, sources, options) {
    var opts = options || {};
    return renderAssistantMarkdown(md, sources || [], {
      marked: window.marked,
      markdownIt: getMarkdownRenderer(),
      katex: window.katex,
      mathJax: window.MathJax,
      sanitize: sanitize,
      citationLabel: opts.citationLabel
    });
  }

  function getMarkdownRenderer() {
    if (markdownRenderer) return markdownRenderer;
    if (typeof window.markdownit === 'function') {
      markdownRenderer = window.markdownit({
        html: false,
        linkify: true,
        typographer: true,
        breaks: true
      });
      return markdownRenderer;
    }
    return null;
  }

  function renderSources(sources) {
    var wrap = document.getElementById('assistant-sources');
    if (!wrap) return;
    wrap.innerHTML = renderSourcesList(sources);
  }

  function renderEmbeddedBlocks(root) {
    root.querySelectorAll('[data-mermaid]').forEach(function (el) {
      ensureMermaid().then(function () {
        if (!window.mermaid) { el.textContent = el.getAttribute('data-mermaid'); return; }
        var id = 'assistant-mermaid-' + Math.random().toString(16).slice(2);
        window.mermaid.render(id, el.getAttribute('data-mermaid') || '')
          .then(function (out) { el.innerHTML = out.svg || ''; })
          .catch(function () { el.textContent = el.getAttribute('data-mermaid') || ''; });
      });
    });
    root.querySelectorAll('canvas[data-chart]').forEach(function (canvas) {
      if (!window.Chart) return;
      try {
        var cfg = JSON.parse(canvas.getAttribute('data-chart') || '{}');
        new window.Chart(canvas.getContext('2d'), cfg);
      } catch (e) {
        canvas.replaceWith(document.createTextNode('Invalid chart JSON'));
      }
    });
  }

  function ensureMermaid() {
    if (window.mermaid && mermaidReady) return Promise.resolve();
    return new Promise(function (resolve) {
      if (window.mermaid) { configureMermaid(); resolve(); return; }
      var script = document.createElement('script');
      script.src = '/vendor/mermaid.min.js';
      script.onload = function () { configureMermaid(); resolve(); };
      script.onerror = resolve;
      document.head.appendChild(script);
    });
  }

  function configureMermaid() {
    if (!window.mermaid) return;
    window.mermaid.initialize({ startOnLoad: false, securityLevel: 'strict', theme: 'default', flowchart: { useMaxWidth: true, htmlLabels: true } });
    mermaidReady = true;
  }

  function generateImage(promptOverride, fromChat) {
    var promptEl = document.getElementById('assistant-image-prompt');
    var out = document.getElementById('assistant-visual-output');
    var prompt = typeof promptOverride === 'string' ? promptOverride.trim() : (promptEl ? promptEl.value.trim() : '');
    if (!prompt && lastAnswer) prompt = 'Create a concise pediatric clinical teaching visual from this answer:\n\n' + lastAnswer.slice(0, 3000);
    if (!prompt) { if (typeof showToast === 'function') showToast('Enter an image prompt or ask a question first', 'error'); return; }
    if (fromChat) setBusy(true, 'Generating image...');
    var loading = fromChat ? appendLoadingMessage('Generating image', 'Creating the requested clinical visual...') : null;
    if (out) out.innerHTML = '<p class="assistant-muted"><i class="fas fa-spinner fa-spin"></i> Generating image...</p>';
    startAssistantImageJob(prompt)
    .then(function (data) {
      if (!data.success || !data.jobId) throw new Error(data.error || 'Image generation failed');
      return waitForImageJob(data.jobId, function (status) {
        if (out && status === 'running') out.innerHTML = '<p class="assistant-muted"><i class="fas fa-spinner fa-spin"></i> Generating image... You can leave the app open or return in a moment.</p>';
      });
    })
    .then(function (data) {
      if (!data.success) throw new Error(data.error || 'Image generation failed');
      var src = data.base64 ? ('data:image/png;base64,' + data.base64) : (data.imageUrl || data.url || '');
      if (!src) throw new Error('No image returned');
      lastGeneratedImageSrc = src;
      exporter.invalidate();
      var downloadUrl = data.downloadUrl || '';
      if (out) out.innerHTML = imageStore.renderGeneratedImage(src, 'Generated clinical visual', downloadUrl);
      if (fromChat) {
        setBusy(false, 'Ready');
        var html = imageStore.renderGeneratedImage(src, 'Generated clinical visual', downloadUrl);
        replaceLoadingMessage(loading, html, [], [], true);
      }
    })
    .catch(function (err) {
      if (fromChat) setBusy(false, 'Error', true);
      if (out) out.innerHTML = '<p class="assistant-muted">' + escapeHtml(err.message) + '</p>';
      if (fromChat) replaceLoadingMessage(loading, 'Image generation failed: ' + err.message);
      if (typeof showToast === 'function') showToast(err.message, 'error');
    });
  }

  function waitForImageJob(jobId, onStatus) {
    var started = Date.now();
    var delay = 1200;
    return new Promise(function (resolve, reject) {
      function poll() {
        fetchAssistantImageJob(jobId).then(function (data) {
          if (!data.success) throw new Error(data.error || 'Image generation failed');
          if (data.status === 'done') { resolve(data); return; }
          if (data.status === 'error') { reject(new Error(data.error || 'Image generation failed')); return; }
          if (typeof onStatus === 'function') onStatus(data.status || 'pending');
          if (Date.now() - started > 180000) { reject(new Error('Image generation timed out. Please try again.')); return; }
          delay = Math.min(delay + 300, 3500);
          setTimeout(poll, delay);
        }).catch(function (err) {
          if (Date.now() - started > 180000) { reject(err); return; }
          setTimeout(poll, delay);
        });
      }
      poll();
    });
  }

  function onAssistantDocumentClick(e) {
    var loadBtn = e.target.closest('[data-assistant-load-chat]');
    if (loadBtn) {
      e.preventDefault();
      loadSavedChat(loadBtn.getAttribute('data-assistant-load-chat'));
      return;
    }
    var deleteBtn = e.target.closest('[data-assistant-delete-chat]');
    if (deleteBtn) {
      e.preventDefault();
      deleteSavedChat(deleteBtn.getAttribute('data-assistant-delete-chat'));
      return;
    }
    var openBtn = e.target.closest('[data-assistant-open-image]');
    if (openBtn) {
      e.preventDefault();
      imageStore.openImagePreview(openBtn.getAttribute('data-assistant-open-image'));
      return;
    }
    var downloadBtn = e.target.closest('[data-assistant-download-image]');
    if (downloadBtn) {
      e.preventDefault();
      imageStore.downloadImage(downloadBtn.getAttribute('data-assistant-download-image'));
      return;
    }
    if (e.target.closest('.assistant-image-modal-close') || e.target.classList.contains('assistant-image-modal')) {
      imageStore.closeImagePreview();
    }
  }

  function clearGeneratedImage() {
    lastGeneratedImageSrc = '';
    imageStore.clear();
    var out = document.getElementById('assistant-visual-output');
    if (out) out.innerHTML = '';
    exporter.invalidate();
  }

  function prepareSidebarImagePrompt(request) {
    var promptEl = document.getElementById('assistant-image-prompt');
    var prompt = buildContextualImagePrompt(request, lastAnswer, lastSources);
    if (promptEl) {
      promptEl.value = prompt;
      promptEl.focus();
    }
    appendMessage('assistant', 'I prepared the image prompt in the **Image / Graph** box on the right. Click **Generate image** there so the visual uses the current answer as context instead of treating this as a separate chat request.');
    setBusy(false, 'Ready');
  }

  function clearConversation() {
    messages = [];
    lastAnswer = '';
    lastSources = [];
    lastGeneratedImageSrc = '';
    exporter.invalidate();
    var wrap = document.getElementById('assistant-messages');
    if (wrap) {
      wrap.innerHTML = renderEmptyState();
      bindExampleButtons(wrap);
    }
    renderSources([]);
    clearGeneratedImage();
    loadSavedChats();
  }

  function cancelAssistantSearch() {
    if (!activeAssistantRequest) return;
    var request = activeAssistantRequest;
    request.abort();
    activeAssistantRequest = null;
    setBusy(false, 'Ready');
    if (request.loading && request.loading.parentNode) {
      replaceLoadingMessage(request.loading, 'Search cancelled.');
    }
  }

  function renderEmptyState() {
    var examples = pickExamples();
    return '<div class="assistant-empty"><i class="fas fa-book-medical"></i>' +
      '<h3>Evidence-first pediatric assistant</h3>' +
      '<p>Ask a real clinical question. Citations stay linked to the source cards on the right.</p>' +
      '<div class="assistant-examples">' + examples.map(function (item) {
        var source = item.sourceTitle ? (' title="Available from: ' + escapeAttr(item.sourceTitle + (item.page ? ', page ' + item.page : '')) + '"') : '';
        return '<button type="button" data-assistant-example="' + escapeAttr(item.prompt) + '"' + source + '>' + escapeHtml(item.label) + '</button>';
      }).join('') + '</div></div>';
  }

  function pickExamples() {
    var examples = dynamicExamples.length ? dynamicExamples.slice() : EMPTY_PROMPT_SETS[Math.floor(Math.random() * EMPTY_PROMPT_SETS.length)].slice();
    for (var i = examples.length - 1; i > 0; i--) {
      var j = Math.floor(Math.random() * (i + 1));
      var tmp = examples[i];
      examples[i] = examples[j];
      examples[j] = tmp;
    }
    return examples.slice(0, 3);
  }

  function bindExampleButtons(root) {
    (root || document).querySelectorAll('[data-assistant-example]').forEach(function (btn) {
      if (btn.getAttribute('data-assistant-bound') === 'true') return;
      btn.setAttribute('data-assistant-bound', 'true');
      btn.addEventListener('click', function () {
        var el = document.getElementById('assistant-input');
        if (el) {
          el.value = btn.getAttribute('data-assistant-example') || '';
          el.focus();
        }
      });
    });
  }

  function copyLastAnswer() {
    if (!lastAnswer) { if (typeof showToast === 'function') showToast('No answer to copy', 'error'); return; }
    navigator.clipboard.writeText(lastAnswer).then(function () {
      if (typeof showToast === 'function') showToast('Copied answer', 'success');
    }).catch(function () {
      if (typeof showToast === 'function') showToast('Copy failed', 'error');
    });
  }

  function exportAnswerPdf() {
    exporter.exportAnswerPdf({
      messages: messages,
      lastAnswer: lastAnswer,
      lastSources: lastSources,
      lastGeneratedImageSrc: lastGeneratedImageSrc
    });
  }

  function saveCurrentChat() {
    if (!messages.length || !lastAnswer) { if (typeof showToast === 'function') showToast('No assistant chat to save yet', 'error'); return; }
    var titleEl = document.getElementById('assistant-save-title');
    var title = String(titleEl && titleEl.value || deriveChatTitle()).trim() || deriveChatTitle();
    setBusy(true, 'Saving chat...');
    saveAssistantChat({
      title: title,
      messages: messages,
      sources: lastSources,
      lastAnswer: lastAnswer,
      generatedImage: imageForSavedChatPayload(lastGeneratedImageSrc)
    })
    .then(function (data) {
      setBusy(false, 'Ready');
      if (!data.success) throw new Error(data.error || 'Save failed');
      hideSavePanel();
      if (typeof showToast === 'function') showToast('Saved chat', 'success');
      loadSavedChats();
    })
    .catch(function (err) {
      setBusy(false, 'Error', true);
      if (typeof showToast === 'function') showToast(err.message, 'error');
    });
  }

  function showSavePanel() {
    if (!messages.length || !lastAnswer) { if (typeof showToast === 'function') showToast('No assistant chat to save yet', 'error'); return; }
    var panel = document.getElementById('assistant-save-panel');
    var titleEl = document.getElementById('assistant-save-title');
    if (!panel || !titleEl) return saveCurrentChat();
    titleEl.value = deriveChatTitle();
    panel.hidden = false;
    titleEl.focus();
    titleEl.select();
  }

  function hideSavePanel() {
    var panel = document.getElementById('assistant-save-panel');
    if (panel) panel.hidden = true;
  }

  function loadSavedChats() {
    fetchSavedAssistantChats()
      .then(function (data) { if (data.success) renderSavedChats(data.chats || []); })
      .catch(function () {});
  }

  function renderSavedChats(chats) {
    var wrap = document.getElementById('assistant-saved-chats');
    if (!wrap) return;
    if (!chats.length) {
      wrap.innerHTML = '<p class="assistant-muted">Saved chats appear here after you click Save chat.</p>';
      return;
    }
    wrap.innerHTML = chats.map(function (chat) {
      return '<div class="assistant-saved-chat">' +
        '<div class="assistant-saved-chat-title">' + escapeHtml(chat.title || 'Saved chat') + '</div>' +
        '<div class="assistant-saved-chat-meta">' + escapeHtml(formatSavedDate(chat.updated_at || chat.created_at)) + '</div>' +
        '<div class="assistant-saved-chat-actions">' +
          '<button type="button" class="btn-sm btn-ghost" data-assistant-load-chat="' + escapeAttr(chat.id) + '">Load</button>' +
          '<button type="button" class="btn-sm btn-ghost" data-assistant-delete-chat="' + escapeAttr(chat.id) + '">Delete</button>' +
        '</div></div>';
    }).join('');
  }

  function loadSavedChat(id) {
    fetchSavedAssistantChat(id)
      .then(function (data) {
        if (!data.success) throw new Error(data.error || 'Load failed');
        restoreSavedChat(data.chat && data.chat.payload || {});
        if (typeof showToast === 'function') showToast('Loaded saved chat', 'success');
      })
      .catch(function (err) { if (typeof showToast === 'function') showToast(err.message, 'error'); });
  }

  function deleteSavedChat(id) {
    var btn = document.querySelector('[data-assistant-delete-chat="' + cssEscape(id) + '"]');
    if (btn && btn.getAttribute('data-confirm-delete') !== 'true') {
      btn.setAttribute('data-confirm-delete', 'true');
      btn.textContent = 'Confirm delete';
      setTimeout(function () {
        if (btn.getAttribute('data-confirm-delete') === 'true') {
          btn.removeAttribute('data-confirm-delete');
          btn.textContent = 'Delete';
        }
      }, 5000);
      return;
    }
    deleteSavedAssistantChat(id)
    .then(function () { loadSavedChats(); if (typeof showToast === 'function') showToast('Deleted saved chat', 'success'); })
    .catch(function () { if (typeof showToast === 'function') showToast('Delete failed', 'error'); });
  }

  function restoreSavedChat(payload) {
    messages = Array.isArray(payload.messages) ? payload.messages.map(function (m) {
      return { role: m.role === 'assistant' ? 'assistant' : 'user', content: String(m.content || ''), sources: Array.isArray(m.sources) ? m.sources : [] };
    }) : [];
    lastSources = Array.isArray(payload.sources) ? payload.sources : [];
    lastAnswer = String(payload.lastAnswer || lastAssistantMessage(messages) || '');
    lastGeneratedImageSrc = String(payload.generatedImage || '');
    var wrap = document.getElementById('assistant-messages');
    if (wrap) {
      wrap.innerHTML = '';
      messages.forEach(function (m) { appendMessageNode(m.role, m.content, m.sources && m.sources.length ? m.sources : lastSources); });
      wrap.scrollTop = wrap.scrollHeight;
    }
    renderSources(lastSources);
    var out = document.getElementById('assistant-visual-output');
    if (out) out.innerHTML = lastGeneratedImageSrc ? imageStore.renderGeneratedImage(lastGeneratedImageSrc, 'Generated clinical visual') : '';
  }

  function appendMessageNode(role, content, sources) {
    var wrap = document.getElementById('assistant-messages');
    if (!wrap) return;
    var row = document.createElement('div');
    row.className = 'assistant-msg ' + role;
    var label = document.createElement('div');
    label.className = 'assistant-msg-label';
    label.textContent = role === 'user' ? 'You' : 'Assistant';
    var bubble = document.createElement('div');
    bubble.className = 'assistant-bubble';
    bubble.innerHTML = role === 'assistant' ? renderMarkdown(content, sources || []) : escapeHtml(content);
    row.appendChild(label);
    row.appendChild(bubble);
    wrap.appendChild(row);
    renderEmbeddedBlocks(bubble);
  }

  function deriveChatTitle() {
    var first = messages.find(function (m) { return m.role === 'user' && m.content; });
    return String(first && first.content || 'Clinical assistant chat').replace(/\s+/g, ' ').trim().slice(0, 80);
  }

  function imageForSavedChatPayload(src) {
    src = String(src || '');
    return /^https?:\/\//i.test(src) ? src : '';
  }

  function lastAssistantMessage(items) {
    for (var i = items.length - 1; i >= 0; i--) if (items[i].role === 'assistant') return items[i].content;
    return '';
  }

  function formatSavedDate(value) {
    try { return new Date(value).toLocaleString(); } catch (e) { return ''; }
  }

  function cssEscape(value) {
    if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(String(value || ''));
    return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
  }

  function setBusy(isBusy, text, isError) {
    assistantBusy = !!isBusy;
    var status = document.getElementById('assistant-status');
    var label = document.getElementById('assistant-status-text');
    var send = document.getElementById('btn-assistant-send');
    var cancel = document.getElementById('btn-assistant-cancel');
    var input = document.getElementById('assistant-input');
    if (status) {
      status.classList.toggle('busy', !!isBusy);
      status.classList.toggle('error', !!isError);
    }
    if (label) label.textContent = text || (isBusy ? 'Working...' : 'Ready');
    if (send) {
      send.disabled = !!isBusy;
      send.innerHTML = isBusy ? '<i class="fas fa-spinner fa-spin"></i> Searching' : '<i class="fas fa-paper-plane"></i> Ask';
    }
    if (cancel) {
      var canCancel = !!isBusy && !!activeAssistantRequest;
      if (canCancel) cancel.removeAttribute('hidden');
      else cancel.setAttribute('hidden', '');
      cancel.disabled = !canCancel;
      cancel.style.display = canCancel ? 'inline-flex' : 'none';
    }
    if (input) input.disabled = !!isBusy;
  }

  function sanitize(html) { return window.DOMPurify ? window.DOMPurify.sanitize(html, { ADD_ATTR: ['target'] }) : html; }
