(function ($) {
  'use strict';

  function escapeHtml(str) {
    if (!str) return '';
    return String(str).replace(/[&<>"']/g, function (c) {
      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }[c];
    });
  }

  var $messages, $input, $sendBtn, $typing;

  var activeAskSocket    = null;
  var activeAskSocketUrl = '';
  var activeStreamState  = null;
  var isOperatorJoined   = false;
  var isLiveModeActive   = false;
  var closingSocketFromUi = false;
  var tabSessionIdCache  = null;
  var pendingSocketConnection = null;
  var socketConnectionRequested = false;
  var TAB_SESSION_STORAGE_KEY = 'mindweco_tab_session_id';

  function isValidUuid(value) {
    return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
      String(value || '')
    );
  }

  function generateUuid() {
    if (window.crypto && typeof window.crypto.randomUUID === 'function') {
      return window.crypto.randomUUID();
    }

    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (character) {
      var random = Math.random() * 16 | 0;
      var value  = character === 'x' ? random : (random & 0x3 | 0x8);
      return value.toString(16);
    });
  }

  function normalizeLanguageCode(language) {
    if (!language) return '';

    return String(language)
      .trim()
      .toLowerCase()
      .split(/[-_]/)[0] || '';
  }

  function restoreTabSessionFromUrl() {
    var currentUrl;

    try {
      currentUrl = new URL(window.location.href);
    } catch (error) {
      return;
    }

    var tabSessionId = currentUrl.searchParams.get('mindweco_tab_session_id') || '';
    if (!isValidUuid(tabSessionId)) {
      return;
    }

    tabSessionIdCache = tabSessionId;

    try {
      sessionStorage.setItem(TAB_SESSION_STORAGE_KEY, tabSessionId);
    } catch (error) {}

    currentUrl.searchParams.delete('mindweco_tab_session_id');
    if (window.history && typeof window.history.replaceState === 'function') {
      window.history.replaceState({}, document.title, currentUrl.pathname + currentUrl.search + currentUrl.hash);
    }
  }

  restoreTabSessionFromUrl();

  function getTabSessionId() {
    if (tabSessionIdCache && isValidUuid(tabSessionIdCache)) {
      return tabSessionIdCache;
    }

    try {
      var storedId = sessionStorage.getItem(TAB_SESSION_STORAGE_KEY);
      if (isValidUuid(storedId)) {
        tabSessionIdCache = storedId;
        return storedId;
      }

      var generatedId = generateUuid();
      sessionStorage.setItem(TAB_SESSION_STORAGE_KEY, generatedId);
      tabSessionIdCache = generatedId;
      return generatedId;
    } catch (error) {
      tabSessionIdCache = tabSessionIdCache || generateUuid();
      return tabSessionIdCache;
    }
  }

  function buildAskSocketRequestData(message, askOnlyCurrentPage, preopenSocket) {
    return new URLSearchParams({
      action:            'mindweco_prepare_ask_socket',
      nonce:             mindwecoFrontend.nonce,
      message:           message,
      post_id:           mindwecoFrontend.post_id,
      tab_session_id:    getTabSessionId(),
      screen_resolution: window.innerWidth + 'x' + window.innerHeight,
      language_accepted: normalizeLanguageCode(navigator.language || navigator.userLanguage || mindwecoFrontend.language || ''),
      timezone:          Intl.DateTimeFormat().resolvedOptions().timeZone,
      platform:          navigator.userAgentData?.platform || navigator.platform || 'unknown',
      stream:            1,
      ask_only_current_page: askOnlyCurrentPage ? 1 : 0,
      preopen_socket:    preopenSocket ? 1 : 0
    });
  }

  function sendSocketPayloadWhenReady(socket, payload) {
    if (!socket || !payload) {
      return;
    }

    var sendPayload = function () {
      if (socket.readyState === WebSocket.OPEN) {
        socket.send(JSON.stringify(payload));
      }
    };

    if (socket.readyState === WebSocket.OPEN) {
      sendPayload();
      return;
    }

    if (socket.readyState === WebSocket.CONNECTING) {
      socket.addEventListener('open', sendPayload, { once: true });
    }
  }

  function openSocketConnection(url, suppressCloseFinish) {
    if (!url) {
      return null;
    }

    if (
      activeAskSocket &&
      activeAskSocketUrl === url &&
      (activeAskSocket.readyState === WebSocket.OPEN ||
        activeAskSocket.readyState === WebSocket.CONNECTING)
    ) {
      return activeAskSocket;
    }

    if (
      activeAskSocket &&
      (activeAskSocket.readyState === WebSocket.OPEN ||
        activeAskSocket.readyState === WebSocket.CONNECTING)
    ) {
      if (suppressCloseFinish) {
        closingSocketFromUi = true;
      }
      try {
        activeAskSocket.close(1000, 'Reconnect');
      } catch (error) {}
    }

    activeAskSocket    = null;
    activeAskSocketUrl = url;

    try {
      activeAskSocket = new WebSocket(url);
    } catch (error) {
      activeAskSocket    = null;
      activeAskSocketUrl = '';
      console.error('MindWebConcierge ask socket error', error);
      return null;
    }

    var socket = activeAskSocket;

    socket.addEventListener('message', function (event) {
      handleSocketData(event.data);
    });

    socket.addEventListener('error', function () {
      console.error('MindWebConcierge ask socket error');
      if (activeStreamState && !activeStreamState.streamClosed) {
        activeStreamState.streamClosed = true;
        activeStreamState.controller.error(new Error('WebSocket ask request failed.'));
        activeStreamState = null;
      }
    });

    socket.addEventListener('close', function (event) {
      if (activeAskSocket === socket) {
        activeAskSocket    = null;
        activeAskSocketUrl = '';
      }
      if (closingSocketFromUi) {
        closingSocketFromUi = false;
        return;
      }
      if (activeStreamState) {
        finishSocketStream(activeStreamState);
      }
    });

    return socket;
  }

  window.MindwecoSessionState = window.MindwecoSessionState || {};
  window.MindwecoSessionState.getTabSessionId = getTabSessionId;

  async function connectChatSocket() {
    socketConnectionRequested = true;

    if (
      activeAskSocket &&
      (activeAskSocket.readyState === WebSocket.OPEN ||
        activeAskSocket.readyState === WebSocket.CONNECTING)
    ) {
      return activeAskSocket;
    }

    if (pendingSocketConnection) {
      return pendingSocketConnection;
    }

    isOperatorJoined    = false;
    isLiveModeActive    = false;
    closingSocketFromUi = false;
    activeStreamState   = null;

    pendingSocketConnection = (async function () {
      try {
        await ensureFreshNonce(true);

        if (!socketConnectionRequested) {
          return null;
        }

        var requestData = buildAskSocketRequestData(
          '',
          $('#mindweco-context-toggle').is(':checked'),
          true
        );

        var response = await fetch(mindwecoFrontend.ajax_url, {
          method:  'POST',
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          body:    requestData.toString()
        });

        var payload = await response.json();
        if (!payload?.success || !payload?.data?.url) {
          throw new Error(payload?.data?.message || 'Socket warmup failed.');
        }

        if (payload.data.conversation_id) {
          sessionStorage.setItem('mindweco_conversation_id', payload.data.conversation_id);
        }

        if (!socketConnectionRequested) {
          return null;
        }

        return openSocketConnection(payload.data.url, false);
      } catch (error) {
        if (socketConnectionRequested) {
          console.error('MindWebConcierge socket warmup failed', error);
        }
        return null;
      } finally {
        pendingSocketConnection = null;
      }
    })();

    return pendingSocketConnection;
  }

  function disconnectChatSocket() {
    socketConnectionRequested = false;
    closingSocketFromUi = !!(
      activeAskSocket &&
      (activeAskSocket.readyState === WebSocket.OPEN ||
        activeAskSocket.readyState === WebSocket.CONNECTING)
    );

    if (closingSocketFromUi) {
      activeAskSocket.close(1000, 'Modal closed');
    }

    activeAskSocket    = null;
    activeAskSocketUrl = '';
    activeStreamState  = null;
    isOperatorJoined   = false;
    isLiveModeActive   = false;

    hideTyping();
    $('.pipeline-steps').empty();
  }


  function bindModalEvents() {
    $(document).on('click', '.mindweco-open-btn, [data-mindweco-open]', function () {
      connectChatSocket();
    });

    $(document).on(
      'click',
      '.mindweco-close-btn, .mindweco-overlay, [data-mindweco-close]',
      function () {
        disconnectChatSocket();
      }
    );

    $(document).on('mindweco:open',  function () { connectChatSocket();    });
    $(document).on('mindweco:close', function () { disconnectChatSocket(); });

    $(document).on('show.bs.modal',  '#mindweco-modal', function () { connectChatSocket();    });
    $(document).on('hide.bs.modal',  '#mindweco-modal', function () { disconnectChatSocket(); });

    $(document).on('keydown', function (e) {
      if (e.key === 'Escape') {
        var $modal = $('#mindweco-modal, .mindweco-modal-wrap');
        if ($modal.is(':visible')) {
          disconnectChatSocket();
        }
      }
    });
  }

  function closeActiveSocket() {
    socketConnectionRequested = false;
    var shouldClose =
      activeAskSocket &&
      (activeAskSocket.readyState === WebSocket.CONNECTING ||
        activeAskSocket.readyState === WebSocket.OPEN);

    closingSocketFromUi = !!shouldClose;
    if (shouldClose) {
      activeAskSocket.close(1000, 'Chat closed');
    }

    activeAskSocket    = null;
    activeAskSocketUrl = '';
    activeStreamState  = null;
    isOperatorJoined   = false;
    isLiveModeActive   = false;

    $('#mindweco-typing').hide();
    $('.pipeline-steps').empty();
  }


  function showTypingIfAiMode() {
    $typing = $('#mindweco-typing');
    if (isOperatorJoined || isLiveModeActive) {
      $typing.hide();
      $('.pipeline-steps').empty();
      return;
    }
    $typing.show();
  }

  function hideTyping() {
    $('#mindweco-typing').hide();
    $('.pipeline-steps').empty();
  }

  function addOperatorDivider(text) {
    $messages = $('#mindweco-messages');
    if (!$messages.length) return;
    var $divider = $('<div class="mindweco-operator-divider"><span></span></div>');
    $divider.find('span').text(text);
    $messages.append($divider);
    $messages.scrollTop($messages[0].scrollHeight);
  }

  function getOperatorAvatarHtml() {
    return (
      '<span class="mindweco-operator-avatar" aria-hidden="true">' +

      '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'+
          '<path d="M3 18v-6a9 9 0 0 1 18 0v6"/>'+
          '<path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3z"/>'+
          '<path d="M3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"/>'+
      '</svg></span>'
    );
  }

  function parseMessageTimestamp(timestamp) {
    if (!timestamp) return null;

    var normalized = String(timestamp);
    if (normalized.indexOf('T') === -1) {
      normalized = normalized.replace(' ', 'T') + 'Z';
    }

    var parsed = new Date(normalized);
    return isNaN(parsed.getTime()) ? null : parsed;
  }

  function formatStepName(step) {
    return String(step || '')
      .replace(/Step$/, '')
      .replace(/([A-Z])/g, ' $1')
      .trim();
  }

  function getPipelineStepsContainer() {
    var typingElement = document.getElementById('mindweco-typing');
    if (!typingElement) return null;
    var stepsContainer = typingElement.querySelector('.pipeline-steps');
    if (!stepsContainer) {
      stepsContainer = document.createElement('div');
      stepsContainer.className = 'pipeline-steps';
      typingElement.insertBefore(stepsContainer, typingElement.firstChild);
    }
    return stepsContainer;
  }

  function showPipelineStep(step, status) {
    var stepsContainer = getPipelineStepsContainer();
    if (!stepsContainer || !step) return;

    if (status === 'completed') {
      var existing = stepsContainer.querySelector(
        '[data-step="' + String(step).replace(/"/g, '\\"') + '"]'
      );
      if (existing) existing.remove();
      return;
    }

    stepsContainer.querySelectorAll('.pipeline-step').forEach(function (el) {
      el.remove();
    });

    var stepEl = document.createElement('div');
    stepEl.className  = 'pipeline-step';
    stepEl.dataset.step = step;
    stepEl.innerHTML =
      '<span class="step-icon">&bull;</span>' +
      '<span class="step-label">' + escapeHtml(formatStepName(step)) + '</span>';
    stepsContainer.appendChild(stepEl);

    if ($messages && $messages.length) {
      $messages.scrollTop($messages[0].scrollHeight);
    }
  }

  function isNonceErrorMessage(message) {
    return /security check failed|invalid nonce/i.test(message || '');
  }

  async function ensureFreshNonce(forceRefresh) {
    if (typeof mindwecoFrontend === 'undefined') throw new Error('mindwecoFrontend unavailable');
    var nonceRefresh = window.MindwecoNonceRefresh;
    if (!nonceRefresh || typeof nonceRefresh.refresh !== 'function') return mindwecoFrontend.nonce;
    if (forceRefresh || !mindwecoFrontend.nonce || !nonceRefresh.lastRefresh) {
      await nonceRefresh.refresh();
    }
    return mindwecoFrontend.nonce;
  }


  async function requestMessageStream(message, askOnlyCurrent, retryOnNonceFailure) {
    await ensureFreshNonce(true);

    var formData = buildAskSocketRequestData(message, askOnlyCurrent, false);

    var response = await fetch(mindwecoFrontend.ajax_url, {
      method:  'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body:    formData.toString()
    });

    var payload      = await response.json();
    var errorMessage = payload?.data?.message || '';
    var isNonceError = isNonceErrorMessage(errorMessage);

    if (retryOnNonceFailure && isNonceError) {
      await ensureFreshNonce(true);
      return requestMessageStream(message, askOnlyCurrent, false);
    }

    if (!payload?.success || !payload?.data?.url || !payload?.data?.payload) {
      throw new Error(errorMessage || 'Request failed.');
    }

    if (payload.data.conversation_id) {
      sessionStorage.setItem('mindweco_conversation_id', payload.data.conversation_id);
    }

    return createWebSocketResponse(payload.data);
  }


  function getSocketMessage(data, hasChunks) {
    if (typeof data !== 'string') return { text: '', done: false, type: '' };

    try {
      var parsed = JSON.parse(data);
      if (typeof parsed === 'string') return { text: parsed, done: false, type: 'text' };

      if (parsed.type === 'pipeline_step') {
        if (isOperatorJoined || isLiveModeActive) {
          hideTyping();
          return { text: '', done: false, type: 'pipeline_step' };
        }
        showTypingIfAiMode();
        showPipelineStep(parsed.step, parsed.status);
        return { text: '', done: false, type: 'pipeline_step' };
      }

      if (parsed.type === 'operator_message') {
        hideTyping();
        isLiveModeActive = true;
        if (!isOperatorJoined) {
          addOperatorDivider('Operator joined chat');
          isOperatorJoined = true;
        }
        if (parsed.text && parsed.text.trim()) {
          addMessage(parsed.text, 'bot', true, {}, { isOperator: true });
        }
        return { text: '', done: true, type: 'operator_message' };
      }

      if (parsed.type === 'live_ended') {
        hideTyping();
        if (isOperatorJoined) addOperatorDivider('Operator disconnected');
        isOperatorJoined = false;         
        isLiveModeActive = false;
        return { text: '', done: true, type: 'live_ended' };
      }

      if (parsed.type === 'live_mode') {
        isLiveModeActive = true;
        hideTyping();
        return { text: '', done: parsed.status === 'sent' || isOperatorJoined, type: 'live_mode' };
      }

      if (parsed.type === 'chunk') {
        return { text: parsed.text || parsed.content || parsed.delta || '', done: false, type: 'chunk' };
      }

      if (parsed.type === 'answer_complete') {
        return {
          text:
            (parsed.answer && !hasChunks ? parsed.answer : '') +
            '\n' +
            JSON.stringify({
              answer_status:     parsed.answer_status || 0,
              sources:           parsed.sources || [],
              followups:         parsed.followups || parsed.follow_ups || [],
              suggested_services: parsed.suggested_services || parsed.suggestedServices || parsed.services || [],
              suggested_questions: parsed.suggested_questions || [],
              usage:             parsed.usage || {}
            }),
          done: true,
          type: 'answer_complete'
        };
      }

      if (parsed.type === 'error') {
        return { text: '', done: true, type: 'error', error: parsed.message || parsed.text || 'Something went wrong.' };
      }

      if (typeof parsed.answer  === 'string') return { text: parsed.answer,  done: false, type: 'answer'  };
      if (typeof parsed.message === 'string') return { text: parsed.message, done: false, type: 'message' };
      if (typeof parsed.content === 'string') return { text: parsed.content, done: false, type: 'content' };
      if (typeof parsed.text    === 'string') return { text: parsed.text,    done: false, type: 'text'    };
      if (typeof parsed.token   === 'string') return { text: parsed.token,   done: false, type: 'token'   };
      if (typeof parsed.delta   === 'string') return { text: parsed.delta,   done: false, type: 'delta'   };

      return { text: '', done: false, type: parsed.type || '' };
    } catch (e) {
      return { text: data, done: data === '[DONE]', type: 'text' };
    }
  }

  function normalizeSocketSources(value) {
    var sourcesMap = {};

    if (Array.isArray(value)) {
      value.forEach(function (source, index) {
        if (!source || typeof source !== 'object') {
          return;
        }

        var sourceKey = source.source_id || source.id || source.key || source.num || (index + 1);
        sourceKey = String(sourceKey || '');
        if (!sourceKey) {
          return;
        }

        sourcesMap[sourceKey] = {
          url: source.url || source.link || source.href || null,
          title: source.title || source.name || source.label || null,
          indexing_item_id: source.indexing_item_id || source.item_id || source.indexingItemId || null
        };
      });

      return sourcesMap;
    }

    if (value && typeof value === 'object') {
      Object.keys(value).forEach(function (key) {
        sourcesMap[String(key)] = value[key];
      });
    }

    return sourcesMap;
  }

  function renderStandaloneSocketAnswer(rawData) {
    if (typeof rawData !== 'string' || !rawData.trim()) {
      return false;
    }

    var parsed;
    try {
      parsed = JSON.parse(rawData);
    } catch (error) {
      return false;
    }

    if (!parsed || (parsed.type !== 'answer_complete' && typeof parsed.answer !== 'string')) {
      return false;
    }

    var answerText = parsed.answer || parsed.message || parsed.content || parsed.text || '';
    answerText = window.MindwecoHelpers.cleanBotText(answerText || '');

    if (!answerText.trim()) {
      return false;
    }

    $messages = $('#mindweco-messages');
    if (!$messages.length) {
      return false;
    }

    if ($messages.find('.mindweco-message').not('.default-welcome-message').length > 0) {
      return false;
    }

    hideTyping();

    var sourcesMap = normalizeSocketSources(parsed.sources);
    var $message    = addMessage(answerText, 'bot', true, sourcesMap);

    if ($message && Object.keys(sourcesMap).length > 0) {
      var messageId = window.MindwecoHelpers.generateMessageId(answerText, 'bot');
      try {
        localStorage.setItem('cit_' + messageId, JSON.stringify(sourcesMap));
      } catch (error) {}
    }

    renderFollowUps(
      normalizeFollowUps(parsed.followups || parsed.follow_ups || parsed.suggested_questions || []),
      'FAQ :'
    );

    return true;
  }

  async function saveSocketResponse(questionId, responseText, answerStatus, answerStatusSource) {
    answerStatus = parseInt(answerStatus, 10);
    if (isNaN(answerStatus) || answerStatus < 0) answerStatus = 0;
    answerStatusSource = String(answerStatusSource || 'default(0)');

    if (!questionId || !responseText.trim()) return Promise.resolve();
    var formData = new URLSearchParams({
      action:      'mindweco_save_socket_response',
      nonce:       mindwecoFrontend.nonce,
      question_id: questionId,
      response:    responseText,
      answer_status: String(answerStatus),
      answer_status_source: answerStatusSource
    });
    return fetch(mindwecoFrontend.ajax_url, {
      method:  'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body:    formData.toString()
    }).catch(function () {});
  }

  function finishSocketStream(state) {
    state = state || activeStreamState;
    if (!state || state.streamClosed) return;

    state.streamClosed = true;
    var streamMetadata = parseStreamMetadata(state.fullText);
    var cleanResponse  = streamMetadata.cleanText || window.MindwecoHelpers.cleanBotText(state.fullText);
    var answerStatus   = typeof streamMetadata.answerStatus === 'number' ? streamMetadata.answerStatus : 0;
    var answerStatusSource = streamMetadata.answerStatusSource || 'default(0)';

    if (!cleanResponse || !cleanResponse.trim()) {
      try { state.controller.close(); } catch (e) {}
      if (activeStreamState === state) activeStreamState = null;
      return;
    }

    var savePromise = state.responseSaved ? Promise.resolve() : saveSocketResponse(state.questionId, cleanResponse, answerStatus, answerStatusSource);
    state.responseSaved = true;

    try { state.controller.close(); } catch (e) {}
    if (activeStreamState === state) activeStreamState = null;
    savePromise.catch(function () {});
  }


  function handleSocketData(rawData) {
    var state         = activeStreamState;
    var socketMessage = getSocketMessage(rawData, state ? state.hasChunks : false);

    if ( ! state && socketMessage.type === 'answer_complete' ) {
      if ( renderStandaloneSocketAnswer( rawData ) ) {
        return;
      }
    }

    if (socketMessage.type === 'error') {
      if (state && !state.streamClosed) {
        state.streamClosed = true;
        state.controller.error(new Error(socketMessage.error || 'Something went wrong.'));
        activeStreamState = null;
      }
      return;
    }

    var text = socketMessage.text;
    if (text && state && !state.streamClosed) {
      if (socketMessage.type === 'chunk') state.hasChunks = true;
      if (socketMessage.type === 'chunk' && !isOperatorJoined && !isLiveModeActive) hideTyping();
      state.fullText += text;
      state.controller.enqueue(state.encoder.encode(text));
    }

    if (socketMessage.done && state) finishSocketStream(state);
  }

  function createWebSocketResponse(config) {
    var encoder = new TextEncoder();

    var stream = new ReadableStream({
      start: function (controller) {
        activeStreamState = {
          controller:    controller,
          encoder:       encoder,
          fullText:      '',
          hasChunks:     false,
          questionId:    config.question_id,
          responseSaved: false,
          streamClosed:  false
        };

        var socket = openSocketConnection(config.url, true);
        if (!socket) {
          if (activeStreamState && !activeStreamState.streamClosed) {
            activeStreamState.streamClosed = true;
            activeStreamState.controller.error(new Error('WebSocket ask request failed.'));
            activeStreamState = null;
          }
          return;
        }

        sendSocketPayloadWhenReady(socket, config.payload);
      }
    });

    return new Response(stream, {
      headers: { 'Content-Type': 'text/plain; charset=utf-8' }
    });
  }

  function firstMarkerIndex(text, markers) {
    var first = -1;
    markers.forEach(function (marker) {
      var index = text.indexOf(marker);
      if (index !== -1 && (first === -1 || index < first)) first = index;
    });
    return first;
  }

  function findJsonMetadataIndex(text) {
    var match = /(?:^|\n)\s*\{\s*"(?:usage|type|answer|answer_status|sources|followups|follow_ups|followup_questions|follow_up_questions|suggested_questions|suggested_services|services)"/.exec(text || '');
    return match ? match.index + match[0].indexOf('{') : -1;
  }

  function normalizeFollowUps(value) {
    if (!Array.isArray(value)) return [];
    return value.map(function (item) {
      if (typeof item === 'string') return item.trim();
      if (item && typeof item === 'object') return String(item.question || item.text || item.title || item.label || '').trim();
      return '';
    }).filter(function (item) { return item.length > 0; });
  }

  function normalizeSuggestedServices(value) {
    if (!Array.isArray(value)) return [];

    return value.map(function (item) {
      if (typeof item === 'string') {
        var url = item.trim();
        return url ? { title: '', description: '', url: url } : null;
      }

      if (!item || typeof item !== 'object') {
        return null;
      }

      var urlValue = item.url || item.link || item.service_url || item.serviceUrl || item.href || '';
      var urlText = String(urlValue || '').trim();
      if (!urlText) {
        return null;
      }

      return {
        title: String(item.title || item.name || item.label || '').trim(),
        description: String(item.description || item.summary || item.text || '').trim(),
        url: urlText
      };
    }).filter(function (item) {
      return item && item.url;
    });
  }

  function extractJsonObject(text, startIndex) {
    var depth = 0, inString = false, escaped = false;
    for (var i = startIndex; i < text.length; i++) {
      var ch = text.charAt(i);
      if (inString) {
        if (escaped)      { escaped = false; }
        else if (ch === '\\') { escaped = true; }
        else if (ch === '"')  { inString = false; }
        continue;
      }
      if      (ch === '"') { inString = true; }
      else if (ch === '{') { depth++; }
      else if (ch === '}') { depth--; if (depth === 0) return text.substring(startIndex, i + 1); }
    }
    return text.substring(startIndex);
  }

  function parseStreamMetadata(fullText) {
    var sourcesMarker  = '\n[SOURCES]';
    var followupsMarker = '\n[FOLLOWUPS]';
    var usageMarker    = '{"usage"';
    var markers        = [sourcesMarker, followupsMarker, usageMarker];
    var firstMarker    = firstMarkerIndex(fullText, markers);
    var jsonMetaIdx    = findJsonMetadataIndex(fullText);

    if (jsonMetaIdx !== -1 && (firstMarker === -1 || jsonMetaIdx < firstMarker)) firstMarker = jsonMetaIdx;

    var metadata = {
      cleanText:  firstMarker === -1 ? fullText.trim() : fullText.substring(0, firstMarker).trim(),
      sourcesMap: {},
      followUps:  [],
      suggestedServices: [],
      answerStatus: 0,
      answerStatusSource: 'default(0)'
    };

    var srcIdx = fullText.indexOf(sourcesMarker);
    if (srcIdx !== -1) {
      var srcStart = srcIdx + sourcesMarker.length;
      var srcEnd   = firstMarkerIndex(fullText.substring(srcStart), [followupsMarker, usageMarker]);
      var srcPart  = srcEnd === -1 ? fullText.substring(srcStart).trim() : fullText.substring(srcStart, srcStart + srcEnd).trim();
      try { metadata.sourcesMap = JSON.parse(srcPart); } catch (e) {}
    }

    var fuIdx = fullText.indexOf(followupsMarker);
    if (fuIdx !== -1) {
      var fuStart = fuIdx + followupsMarker.length;
      var fuEnd   = firstMarkerIndex(fullText.substring(fuStart), [sourcesMarker, usageMarker]);
      var fuPart  = fuEnd === -1 ? fullText.substring(fuStart).trim() : fullText.substring(fuStart, fuStart + fuEnd).trim();
      try { metadata.followUps = normalizeFollowUps(JSON.parse(fuPart)); } catch (e) {}
    }

    var usageIdx = fullText.indexOf(usageMarker);
    if (usageIdx === -1) usageIdx = jsonMetaIdx;
    if (usageIdx !== -1) {
      try {
        var parsedUsage = JSON.parse(extractJsonObject(fullText, usageIdx));
        if (typeof parsedUsage.answer_status !== 'undefined') {
          var parsedAnswerStatus = parseInt(parsedUsage.answer_status, 10);
          if (isNaN(parsedAnswerStatus)) {
            metadata.answerStatusSource = 'answer_complete.answer_status (invalid)';
          } else {
            metadata.answerStatus = parsedAnswerStatus;
            metadata.answerStatusSource = 'answer_complete.answer_status';
          }
        }
        if (Array.isArray(parsedUsage.sources)) {
          parsedUsage.sources.forEach(function (src) {
            metadata.sourcesMap[String(src.source_id)] = {
              url:              (src.url && src.url !== 'string') ? src.url : null,
              title:            src.title || null,
              indexing_item_id: src.indexing_item_id || src.item_id || src.indexingItemId || null
            };
          });
        } else if (parsedUsage.sources && typeof parsedUsage.sources === 'object') {
          metadata.sourcesMap = parsedUsage.sources;
        }
        var fuKeys = ['followups','follow_ups','followup_questions','follow_up_questions','suggested_questions'];
        for (var i = 0; i < fuKeys.length; i++) {
          if (Array.isArray(parsedUsage[fuKeys[i]])) {
            metadata.followUps = normalizeFollowUps(parsedUsage[fuKeys[i]]);
            break;
          }
        }

        var serviceKeys = ['suggested_services', 'suggestedServices', 'services'];
        for (var j = 0; j < serviceKeys.length; j++) {
          if (Array.isArray(parsedUsage[serviceKeys[j]])) {
            metadata.suggestedServices = normalizeSuggestedServices(parsedUsage[serviceKeys[j]]);
            break;
          }
        }
      } catch (e) {}
    }

    return metadata;
  }


  function showDefaultMessage() {
    var $msg = $('#mindweco-messages');
    if (!$msg.length) return;

    var result    = window.MindwecoGreetings.get(mindwecoFrontend.language);
    var timestamp = window.MindwecoHelpers.formatTimestamp();
    var summary   = mindwecoFrontend.summary || '';

    var lines = summary.split('\n').map(function (l) { return l.trim(); });
    var branches = [], beforeLines = [], afterLines = [], foundBullet = false;

    lines.forEach(function (line) {
      if (!line) return;
      if (/^[•\-\*]/.test(line)) {
        foundBullet = true;
        var clean    = line.replace(/^[•\-\*]\s*/, '').trim();
        if (!clean) return;
        var colonIdx = clean.indexOf(':');
        branches.push(colonIdx > 0
          ? { title: clean.substring(0, colonIdx).trim(), sub: clean.substring(colonIdx + 1).trim() }
          : { title: clean, sub: '' });
      } else {
        if (!foundBullet) beforeLines.push(line);
        else              afterLines.push(line);
      }
    });

    var beforeHtml = beforeLines.length ? '<div class="mindweco-beforeLineHtml">' + beforeLines.join('<br>') + '</div>' : '';
    var afterHtml  = afterLines.length  ? '<div class="mindweco-afterLineHtml">'  + afterLines.join('<br>')  + '</div>' : '';

    if (branches.length === 0) {
      $msg.append($(
        '<div class="mindweco-message bot-message default-welcome-message">' +
        '<div class="message-avatar"><img src="' + mindwecoFrontend.bot_avatar + '" class="mindweco-avatar-img" /></div>' +
        '<div class="message-wrapper"><div class="message-content">' +
        '<strong>' + result.emoji + ' ' + result.greeting + '!</strong>' + beforeHtml + afterHtml +
        '</div><span class="message-timestamp">' + timestamp + '</span></div></div>'
      ));
      return;
    }

    var chipsHtml = branches.map(function (b, i) {
      return (
        '<div class="mwc-chip mwc-row" data-title="' + b.title + '" style="animation-delay:' + (i * 0.12).toFixed(2) + 's">' +
        '<span class="mwc-dot"></span><span class="mwc-label">' + b.title + '</span><span class="mwc-arr">→</span></div>'
      );
    }).join('');

    var $message = $(
      '<div class="mindweco-message bot-message default-welcome-message">' +
      '<div class="message-avatar"><img src="' + mindwecoFrontend.bot_avatar + '" class="mindweco-avatar-img" /></div>' +
      '<div class="message-wrapper"><div class="message-content" style="padding:12px 14px">' +
      '<strong>' + result.emoji + ' ' + result.greeting + '!</strong>' +
      beforeHtml + '<div class="mwc-wrap">' + chipsHtml + '</div>' + afterHtml +
      '</div><span class="message-timestamp">' + timestamp + '</span></div></div>'
    );

    $msg.append($message);

    $message.on('click', '.mwc-chip', function () {
      var title = $(this).data('title');
      $('.mwc-chip').addClass('disabled').css('pointer-events', 'none');
      $('#mindweco-input').val('Tell me about: ' + title);
      setTimeout(function () {
        if (window.MindwecoMessages && window.MindwecoMessages.send) window.MindwecoMessages.send();
      }, 600);
    });
  }


  function parseMarkdown(text) {
    if (window.marked && typeof marked.parse === 'function') return marked.parse(text || '');
    return escapeHtml(text || '').replace(/\n/g, '<br>');
  }

  function renderBotMarkdown(text, sourcesMap) {
    return parseMarkdown(
      window.MindwecoCitations.render(text || '', sourcesMap || {}, { preserveMarkdown: true })
    );
  }


  function addMessage(text, type, scrollToBottom, sourcesMap, options) {
    if (scrollToBottom === undefined) scrollToBottom = true;
    sourcesMap = sourcesMap || {};
    options    = options    || {};
    if (!text || text.trim() === '') return null;

    var $msg = $('#mindweco-messages');
    if (!$msg.length) return null;

    var avatar = options.isOperator
      ? getOperatorAvatarHtml()
      : type === 'bot'
        ? '<img src="' + mindwecoFrontend.bot_avatar  + '" class="mindweco-avatar-img" />'
        : '<img src="' + mindwecoFrontend.user_avatar + '" class="mindweco-avatar-img" />';

    var messageClass = (type === 'bot' ? 'bot-message' : 'user-message') + (options.isOperator ? ' operator-message' : '');
    var parsedTimestamp = parseMessageTimestamp(options.timestamp);
    var time         = parsedTimestamp
      ? window.MindwecoHelpers.formatTimestamp(parsedTimestamp)
      : window.MindwecoHelpers.formatTimestamp();
    var messageId    = window.MindwecoHelpers.generateMessageId(text, type);
    var renderedText = type === 'bot' ? renderBotMarkdown(text, sourcesMap) : parseMarkdown(text);

    var pinIconSvg   = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" class="pin-icon" fill="currentColor"><path d="M10.36 16.48l-2-3.16 1.12-6.12c0.04-0.24 0-0.48-0.16-0.68s-0.4-0.32-0.64-0.32h-6.8c-0.24 0-0.48 0.12-0.64 0.32s-0.24 0.44-0.16 0.68l1.16 6.12-2.08 3.24c-0.16 0.24-0.16 0.6-0.04 0.84 0.16 0.28 0.44 0.44 0.72 0.44h3.6v7.12c0 0.48 0.36 0.84 0.84 0.84s0.84-0.36 0.84-0.84v-7.12h3.6c0.48 0 0.84-0.36 0.84-0.84 0-0.2-0.12-0.4-0.2-0.52zM2.4 16.16l1.4-2.2c0.12-0.16 0.16-0.4 0.12-0.6l-1.04-5.48h4.84l-1.04 5.44c-0.04 0.2 0 0.44 0.12 0.6l1.4 2.2-5.8 0.04z"/></svg>';
    var unpinIconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" class="unpin-icon" fill="currentColor" style="display:none;"><path d="M20 14.274V15h-7v7l-.5 1-.5-1v-6.172L13.828 14h5.163c-.051-.65-.332-.856-1.333-1.515L16.93 12h-1.1l1.16-1.161.927.618c1.302.868 2.084 1.252 2.084 2.817zm2.4-10.966L3.307 22.399l-.707-.707L9.293 15H5v-.726c0-1.565.782-1.95 2.084-2.817l1.147-.765L10 4l-1.522-.43A2.029 2.029 0 0 1 7 1.619V1h11v.618a2.029 2.029 0 0 1-1.478 1.953L15 4l1.107 4.186L21.692 2.6zM10.137 3h4.724l1.388-.392A1.033 1.033 0 0 0 16.926 2H8.074a1.033 1.033 0 0 0 .676.608zm-.954 8h4.109l1.995-1.995L13.966 4h-2.931zm1.109 3l2-2H8.07l-.73.485c-1 .659-1.28.866-1.332 1.515z"/></svg>';
    var copyIconSvg  = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="none" viewBox="0 0 24 24" height="14" width="14"><path data-testid="copy-icon" fill="currentColor" d="M2 22h16V6H2v16ZM4 8h12v12H4V8Z"></path><path fill="currentColor" d="M6 2v2h14v14h2V2H6Z"></path></svg>';

    var actionButtons = type === 'bot'
      ? '<button class="copy-response" title="Copy">' + copyIconSvg + '</button>' +
        '<button class="pin-response" title="Pin your response">' + pinIconSvg + unpinIconSvg + '</button>'
      : '';

    var $message = $(
      '<div class="mindweco-message ' + messageClass + '" data-message-id="' + messageId + '">' +
      '<div class="message-avatar">' + avatar + '</div>' +
      '<div class="message-wrapper">' +
      '<div class="message-content">' + renderedText + '</div>' +
      '<div class="d-flex message-actions">' +
      '<span class="message-timestamp">' + time + '</span>' +
      actionButtons +
      '</div></div></div>'
    );

    $msg.append($message);
    if (scrollToBottom) $msg.scrollTop($msg[0].scrollHeight);

    if (type === 'bot') {
      var $content = $message.find('.message-content');
      window.MindwecoCitations.enrich($content, sourcesMap, { updatePanel: options.updateSourcesPanel !== false });
    }

    return $message;
  }

  function renderFollowUps(followUps, headingText) {
    if (!Array.isArray(followUps) || followUps.length === 0) return;
    var labelText = headingText || 'Suggested questions :';

    var buttonsHtml = followUps.map(function (q) {
      return '<button class="followup-btn">' + escapeHtml(q) + '</button>';
    }).join('');

    var $fc = $(
      '<div class="followup-container">' +
      '<span class="suggestion-text">' +
      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="20" height="16" fill="currentColor">' +
      '<path d="M256 0C150 0 64 86 64 192c0 73.6 47.4 136 112 161.3V400c0 17.7 14.3 32 32 32h96c17.7 0 32-14.3 32-32v-46.7c64.6-25.3 112-87.7 112-161.3 0-106-86-192-192-192zm48 400h-96v-32h96v32zm48-88c-14.3 8.3-32.5 14.7-48 14.7s-33.7-6.4-48-14.7V272h96v40zM256 64c70.7 0 128 57.3 128 128 0 48.6-28.7 91.3-72 112H200c-43.3-20.7-72-63.4-72-112 0-70.7 57.3-128 128-128z"/>' +
      '</svg> ' + escapeHtml(labelText) +
      '</span>' + buttonsHtml + '</div>'
    );

    $messages.append($fc);

    $fc.find('.followup-btn').on('click', function () {
      if ($sendBtn.prop('disabled')) return;
      var $btn = $(this);
      $fc.find('.followup-btn').prop('disabled', true);
      $btn.addClass('followup-selected');
      $input.val($btn.text());
      sendMessage().finally(function () {
        $fc.find('.followup-btn').prop('disabled', false);
        $btn.removeClass('followup-selected');
      });
    });
  }

  function renderSuggestedService(services){
    if (!window.MindwecoUI || typeof window.MindwecoUI.setSuggestedServices !== 'function') {
      return;
    }

    var normalizedServices = normalizeSuggestedServices(services);
    window.MindwecoUI.setSuggestedServices(normalizedServices);
  }
  
  function scrollMessageTopIntoView($message) {
    if (!$message || !$message.length || !$messages || !$messages.length) return;
    $messages.scrollTop($message[0].offsetTop - $messages[0].offsetTop);
  }


  async function sendMessage() {
    $messages = $('#mindweco-messages');
    $input    = $('#mindweco-input');
    $sendBtn  = $('.mindweco-send-btn');
    $typing   = $('#mindweco-typing');

    var message = $input.val().trim();
    if (!message) return;

    $('.mwc-wrap .mwc-chip').addClass('disabled').css('pointer-events', 'none');
    addMessage(message, 'user', true, {});
    $input.val('').css('height', 'auto');
    $sendBtn.prop('disabled', true);

    showTypingIfAiMode();

    var askOnlyCurrent = $('#mindweco-context-toggle').is(':checked');

    var pinIconSvg   = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="18" class="pin-icon" fill="currentColor"><path d="M10.36 16.48l-2-3.16 1.12-6.12c0.04-0.24 0-0.48-0.16-0.68s-0.4-0.32-0.64-0.32h-6.8c-0.24 0-0.48 0.12-0.64 0.32s-0.24 0.44-0.16 0.68l1.16 6.12-2.08 3.24c-0.16 0.24-0.16 0.6-0.04 0.84 0.16 0.28 0.44 0.44 0.72 0.44h3.6v7.12c0 0.48 0.36 0.84 0.84 0.84s0.84-0.36 0.84-0.84v-7.12h3.6c0.48 0 0.84-0.36 0.84-0.84 0-0.2-0.12-0.4-0.2-0.52zM2.4 16.16l1.4-2.2c0.12-0.16 0.16-0.4 0.12-0.6l-1.04-5.48h4.84l-1.04 5.44c-0.04 0.2 0 0.44 0.12 0.6l1.4 2.2-5.8 0.04z"/></svg>';
    var unpinIconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" class="unpin-icon" fill="currentColor" style="display:none;"><path d="M20 14.274V15h-7v7l-.5 1-.5-1v-6.172L13.828 14h5.163c-.051-.65-.332-.856-1.333-1.515L16.93 12h-1.1l1.16-1.161.927.618c1.302.868 2.084 1.252 2.084 2.817zm2.4-10.966L3.307 22.399l-.707-.707L9.293 15H5v-.726c0-1.565.782-1.95 2.084-2.817l1.147-.765L10 4l-1.522-.43A2.029 2.029 0 0 1 7 1.619V1h11v.618a2.029 2.029 0 0 1-1.478 1.953L15 4l1.107 4.186L21.692 2.6zM10.137 3h4.724l1.388-.392A1.033 1.033 0 0 0 16.926 2H8.074a1.033 1.033 0 0 0 .676.608zm-.954 8h4.109l1.995-1.995L13.966 4h-2.931zm1.109 3l2-2H8.07l-.73.485c-1 .659-1.28.866-1.332 1.515z"/></svg>';
    var copyIconSvg  = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="none" viewBox="0 0 24 24" height="14" width="14"><path data-testid="copy-icon" fill="currentColor" d="M2 22h16V6H2v16ZM4 8h12v12H4V8Z"></path><path fill="currentColor" d="M6 2v2h14v14h2V2H6Z"></path></svg>';

    try {
      var response = await requestMessageStream(message, askOnlyCurrent, true);
      var reader   = response.body.getReader();
      var decoder  = new TextDecoder('utf-8');

      var $botMsg = $(
        '<div class="mindweco-message bot-message">' +
        '<div class="message-avatar"><img src="' + mindwecoFrontend.bot_avatar + '" class="mindweco-avatar-img" /></div>' +
        '<div class="message-wrapper">' +
        '<div class="message-content"></div>' +
        '<div class="d-flex message-actions"><span class="message-timestamp">' + window.MindwecoHelpers.formatTimestamp() + '</span></div>' +
        '</div></div>'
      );

      var $botContent = $botMsg.find('.message-content');
      var fullText    = '';
      var sourcesMap  = {};
      var summaryText = (mindwecoFrontend.summary || '').replace(/\s+/g, ' ').trim();

      while (true) {
        var chunk = await reader.read();
        if (chunk.done) break;
        fullText += decoder.decode(chunk.value, { stream: true });

        var markerIndex = firstMarkerIndex(fullText, ['\n[SOURCES]', '\n[FOLLOWUPS]', '{"usage"']);
        var jsonMetaIdx = findJsonMetadataIndex(fullText);
        if (jsonMetaIdx !== -1 && (markerIndex === -1 || jsonMetaIdx < markerIndex)) markerIndex = jsonMetaIdx;

        var displayText = markerIndex === -1 ? fullText : fullText.substring(0, markerIndex);
        var rendered    = parseMarkdown(displayText.replace(/\[(\d+)\]/g, ''));

        if (rendered.trim()) {
          $botContent.html(rendered);
          if (!$botMsg.parent().length) {
            $messages.append($botMsg);
            scrollMessageTopIntoView($botMsg);
          }
        }
      }

      var streamMetadata = parseStreamMetadata(fullText);
      var cleanText      = streamMetadata.cleanText;
      sourcesMap         = streamMetadata.sourcesMap;

      if (!cleanText || !cleanText.trim()) { $botMsg.remove(); return; }

      var messageId = window.MindwecoHelpers.generateMessageId(cleanText, 'bot');
      $botMsg.attr('data-message-id', messageId);
      if (Object.keys(sourcesMap).length > 0) localStorage.setItem('cit_' + messageId, JSON.stringify(sourcesMap));

      var cleanNorm   = cleanText.replace(/\s+/g, ' ').trim();
      var hasCalendly = !!(mindwecoData?.show_calendly && mindwecoData.calendly_url);

      if (summaryText && cleanNorm === summaryText) {
        $botMsg.remove();
        window.MindwecoMessages.showDefault();
      } else {
        if (!$botMsg.parent().length) { $messages.append($botMsg); scrollMessageTopIntoView($botMsg); }
        $botContent.html(renderBotMarkdown(cleanText, sourcesMap));
        await window.MindwecoCitations.enrich($botContent, sourcesMap);
        if (hasCalendly && !$('#mwc-sources-body').is(':visible') && window.MindwecoCitations?.switchToCalendly) {
          window.MindwecoCitations.switchToCalendly();
        }
      }

      renderFollowUps(streamMetadata.followUps);
      renderSuggestedService(streamMetadata.suggestedServices);

      $botMsg.find('.message-actions').append(
        '<button class="copy-response" title="Copy">'  + copyIconSvg + '</button>' +
        '<button class="pin-response" title="Pin">' + pinIconSvg + unpinIconSvg + '</button>'
      );

      if (window.MindwecoHistory) window.MindwecoHistory.restorePinnedMessages();

    } catch (err) {
      var errorMessage = err?.message || '';
      addMessage(
        isNonceErrorMessage(errorMessage)
          ? 'The connection was refreshed. Please send your message again.'
          : errorMessage || 'Something went wrong. Please try again.',
        'bot', true, {}
      );
    } finally {
      $sendBtn.prop('disabled', false);
      if (!isOperatorJoined && !isLiveModeActive) $typing.hide();   
      $('.mwc-chip').removeClass('disabled').css('pointer-events', 'auto');
    }
  }


  function init() {
    $messages = $('#mindweco-messages');
    $input    = $('#mindweco-input');
    $sendBtn  = $('.mindweco-send-btn');
    $typing   = $('#mindweco-typing');

    bindModalEvents();

    $sendBtn.on('click', sendMessage);

    $input.on('keydown', function (e) {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        if ($sendBtn.prop('disabled')) return;
        sendMessage();
      }
    });

    $input.on('input', function () {
      this.style.height = 'auto';
      this.style.height = Math.min(this.scrollHeight, 160) + 'px';
    });

    $(document).on('click', '.copy-response', function () {
      var $btn  = $(this);
      var $cont = $btn.closest('.mindweco-message').find('.message-content').clone();
      $cont.find('.mindweco-citation-badge').remove();
      var plainText = $cont.html()
        .replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '')
        .replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').trim();

      navigator.clipboard.writeText(plainText).then(function () {
        var orig = $btn.html();
        $btn.html('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16"><path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/></svg>');
        $btn.addClass('copied');
        setTimeout(function () { $btn.html(orig); $btn.removeClass('copied'); $input.focus(); }, 2000);
      }).catch(function (err) { MindwecoToast.error('Failed to copy:', err); });
    });


    $(document).on('click', '.pin-response', function () {
      var $btn       = $(this);
      var $message   = $btn.closest('.mindweco-message');
      var messageId  = $message.attr('data-message-id');
      var isPinned   = $message.hasClass('pinned');
      if (isPinned) {
        $message.removeClass('pinned');
        localStorage.removeItem('pinned_' + messageId);
        $btn.find('.pin-icon').show();
        $btn.find('.unpin-icon').hide();
      } else {
        $message.addClass('pinned');
        localStorage.setItem('pinned_' + messageId, '1');
        $btn.find('.pin-icon').hide();
        $btn.find('.unpin-icon').show();
      }
    });
  }


  window.MindwecoMessages = {
    init:              init,
    add:               addMessage,
    send:              sendMessage,
    showDefault:       showDefaultMessage,
    closeActiveSocket: closeActiveSocket,
    connectSocket:     connectChatSocket,
    disconnectSocket:  disconnectChatSocket
  };

})(jQuery);
