UNPKG

@ticketping/chat-widget

Version:

Customer support chat widget for Ticketping - Intercom-like experience with real-time messaging

3,099 lines 114 kB
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
  for (var prop in b || (b = {}))
    if (__hasOwnProp.call(b, prop))
      __defNormalProp(a, prop, b[prop]);
  if (__getOwnPropSymbols)
    for (var prop of __getOwnPropSymbols(b)) {
      if (__propIsEnum.call(b, prop))
        __defNormalProp(a, prop, b[prop]);
    }
  return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __async = (__this, __arguments, generator) => {
  return new Promise((resolve, reject) => {
    var fulfilled = (value) => {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    };
    var rejected = (value) => {
      try {
        step(generator.throw(value));
      } catch (e) {
        reject(e);
      }
    };
    var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
    step((generator = generator.apply(__this, __arguments)).next());
  });
};
/*!
 * Ticketping Chat Widget v1.0.0
 * (c) 2024 Ticketping
 * Licensed under MIT
 */
function createDOMElement(tag, attributes = {}) {
  const element = document.createElement(tag);
  if (!attributes) {
    return element;
  }
  Object.entries(attributes).forEach(([key, value]) => {
    if (value === null || value === void 0) {
      return;
    }
    if (key === "className") {
      element.className = value;
    } else if (key === "textContent") {
      element.textContent = value;
    } else if (key === "innerHTML") {
      element.innerHTML = value;
    } else if (key.startsWith("on") && typeof value === "function") {
      const eventName = key.slice(2).toLowerCase();
      element.addEventListener(eventName, value);
    } else if (typeof value === "boolean") {
      if (value) {
        element.setAttribute(key, key);
        element[key] = true;
      } else {
        element[key] = false;
      }
    } else {
      element.setAttribute(key, value);
    }
  });
  return element;
}
const DEFAULT_CONFIG = {
  // Required
  appId: null,
  teamSlug: null,
  teamLogoIcon: null,
  // API Configuration
  apiBase: "https://api.ticketping.com",
  wsBase: "wss://ws.ticketping.com",
  // Authentication
  userJWT: null,
  enableSecureMode: true,
  // Widget Appearance
  position: "bottom-right",
  // 'bottom-right' | 'bottom-left'
  theme: "default",
  // 'default' | 'dark' | 'light'
  zIndex: 999999,
  // Widget Behavior
  autoOpen: false,
  enableTypingIndicators: true,
  enableFileUpload: true,
  enableEmojis: true,
  // File Upload
  maxFileSize: 10 * 1024 * 1024,
  // 10MB
  allowedFileTypes: [
    "image/jpeg",
    "image/png",
    "image/gif",
    "application/pdf",
    "application/msword",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    "text/plain"
  ],
  // Messages
  maxMessageLength: 5e3,
  enableMarkdown: false,
  enableLinkPreviews: true,
  // Conversations
  showConversationHistory: true,
  maxConversationsStored: 50,
  autoDeleteAfterDays: 30,
  // Notifications
  enableSoundNotifications: true,
  enableBrowserNotifications: false,
  // Analytics
  analytics: true,
  trackUserInteractions: true,
  // Localization
  locale: "en",
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  // Development
  debug: false,
  logLevel: "error",
  // Custom Labels
  labels: {
    // Header
    headerTitle: "Ticketping",
    headerSubtitle: "We're here to help!",
    // Tabs
    homeTab: "Home",
    messagesTab: "Messages",
    // Home Tab
    welcomeTitle: "👋 Welcome!",
    welcomeMessage: "Hi there! How can we help you today?",
    startConversationButton: "Send us a message",
    // Messages
    messageInputPlaceholder: "Type your message...",
    sendButton: "Send",
    attachButton: "Attach file",
    // Status Messages
    typingIndicator: "Support is typing...",
    agentOnline: "We're online and ready to help!",
    agentOffline: "We'll get back to you soon!",
    connectionLost: "Connection lost. Trying to reconnect...",
    // Errors
    fileTooLarge: "File size exceeds maximum limit",
    fileTypeNotAllowed: "File type not supported",
    messageTooLong: "Message is too long",
    sendError: "Failed to send message. Please try again.",
    loadError: "Failed to load conversation",
    // Empty States
    noConversations: "No conversations yet",
    noConversationsDescription: "Start a conversation to get help from our support team.",
    // Help Articles
    helpArticlesTitle: "Popular articles",
    helpArticles: [
      {
        title: "Getting started guide",
        url: "/help/getting-started",
        icon: "article"
      },
      {
        title: "How to create tickets",
        url: "/help/create-tickets",
        icon: "check"
      },
      {
        title: "Best practices",
        url: "/help/best-practices",
        icon: "star"
      }
    ]
  },
  // Custom CSS
  customCSS: null,
  // Callbacks
  onReady: null,
  onOpen: null,
  onClose: null,
  onMessageSent: null,
  onMessageReceived: null,
  onConversationStarted: null,
  onError: null
};
const API_ENDPOINTS = {
  auth: "/api/v1/jwt/auth/",
  newChatSession: "/api/v1/chat-session/create/",
  conversations: "/api/v1/chat-sessions/",
  messages: "/messages",
  fileUpload: "/api/v1/chat-session/file-upload/",
  analytics: "/analytics",
  teamWidgetSettings: "/api/v1/team/widget/"
};
const WEBSOCKET_EVENTS = {
  // Client -> Server
  AUTH: "auth",
  TYPING_START: "typing_start",
  TYPING_STOP: "typing_stop",
  JOIN_CONVERSATION: "join_conversation",
  LEAVE_CONVERSATION: "leave_conversation",
  FILE_ATTACHMENT: "file_attachment",
  MARK_READ: "mark_read",
  // Server -> Client
  SERVER_STATE: "server_session_state",
  SERVER_MESSAGE_HISTORY: "server_message_history",
  SERVER_MESSAGE: "server_message",
  SERVER_AGENT_STATUS: "server_agent_status",
  SERVER_AGENT_JOINED: "server_agent_joined",
  SERVER_AGENT_LEFT: "server_agent_left",
  SERVER_CONVERSATION_UPDATED: "server_conversation_updated",
  SERVER_AUTH_SUCCESS: "server_auth_success",
  SERVER_AUTH_FAILED: "server_auth_failed",
  SERVER_ANONYMOUS_AUTH_SUCCESS: "server_anonymous_auth_success",
  SERVER_ANONYMOUS_AUTH_FAILED: "server_anonymous_auth_failed",
  PONG: "pong",
  ERROR: "error"
};
const STORAGE_KEYS = {
  CONVERSATIONS: "ticketping_conversations",
  USER_DATA: "ticketping_user",
  SETTINGS: "ticketping_settings",
  DEVICE_ID: "ticketping_device_id",
  UNREAD_CONVERSATIONS: "ticketping_unread_conversations"
};
const CSS_CLASSES = {
  BUBBLE: "ticketping-chat-bubble",
  WINDOW: "ticketping-chat-window",
  OPEN: "open"
};
class ChatBubble {
  constructor(container, options = {}) {
    if (!container) {
      console.error("ChatBubble: Container is required");
      this.container = null;
      this.element = null;
      this.notificationBadge = null;
      this.isOpen = false;
      this.options = __spreadValues({
        onClick: () => {
        },
        showNotificationBadge: false,
        notificationCount: 0
      }, options);
      return;
    }
    this.container = container;
    this.options = __spreadValues({
      onClick: () => {
      },
      showNotificationBadge: false,
      notificationCount: 0
    }, options);
    this.element = null;
    this.notificationBadge = null;
    this.isOpen = false;
    this.render();
    this.attachEventListeners();
  }
  render() {
    if (!this.container) {
      return;
    }
    this.element = createDOMElement("button", {
      className: `${CSS_CLASSES.BUBBLE}`,
      "aria-label": "Open chat",
      "aria-expanded": "false",
      role: "button",
      tabindex: "0"
    });
    const iconSvg = this.createChatIcon();
    this.element.appendChild(iconSvg);
    if (this.options.showNotificationBadge) {
      this.createNotificationBadge();
    }
    this.container.appendChild(this.element);
  }
  createChatIcon() {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("viewBox", "0 0 18 18");
    svg.setAttribute("width", "32");
    svg.setAttribute("height", "32");
    const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
    path.setAttribute("d", "M14.25,2.25H3.75c-1.105,0-2,.896-2,2v7c0,1.104,.895,2,2,2h2v3l3.75-3h4.75c1.105,0,2-.896,2-2V4.25c0-1.104-.895-2-2-2Z");
    path.setAttribute("fill", "none");
    path.setAttribute("stroke", this.options.iconColor || "#ffffff");
    path.setAttribute("stroke-linecap", "round");
    path.setAttribute("stroke-linejoin", "round");
    path.setAttribute("stroke-width", "1.5");
    svg.appendChild(path);
    return svg;
  }
  createCloseIcon() {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("viewBox", "0 0 24 24");
    svg.setAttribute("width", "24");
    svg.setAttribute("height", "24");
    const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
    path.setAttribute("d", "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z");
    svg.appendChild(path);
    return svg;
  }
  createNotificationBadge() {
    this.notificationBadge = createDOMElement("div", {
      className: "notification-badge"
    });
    this.element.appendChild(this.notificationBadge);
  }
  attachEventListeners() {
    this.element.addEventListener("click", () => {
      this.handleClick();
    });
    this.element.addEventListener("keydown", (e) => {
      if (e.key === "Enter" || e.key === " ") {
        e.preventDefault();
        this.handleClick();
      }
    });
  }
  handleClick() {
    this.options.onClick();
    if (this.notificationBadge) {
      this.hideNotificationBadge();
    }
  }
  setOpen(isOpen) {
    this.isOpen = isOpen;
    if (!this.element) {
      return;
    }
    if (isOpen) {
      this.element.classList.add(CSS_CLASSES.OPEN);
      this.element.setAttribute("aria-expanded", "true");
      this.element.setAttribute("aria-label", "Close chat");
      const svg = this.element.querySelector("svg");
      if (svg) {
        this.element.removeChild(svg);
        this.element.appendChild(this.createCloseIcon());
      }
    } else {
      this.element.classList.remove(CSS_CLASSES.OPEN);
      this.element.setAttribute("aria-expanded", "false");
      this.element.setAttribute("aria-label", "Open chat");
      const svg = this.element.querySelector("svg");
      if (svg) {
        this.element.removeChild(svg);
        this.element.appendChild(this.createChatIcon());
      }
    }
  }
  showNotificationBadge(count = 1) {
    if (!this.element) {
      return;
    }
    if (!this.notificationBadge) {
      this.createNotificationBadge();
    }
    this.options.notificationCount = count;
    if (count > 0) {
      this.notificationBadge.style.display = "flex";
    } else {
      this.notificationBadge.style.display = "none";
    }
  }
  hideNotificationBadge() {
    if (this.notificationBadge) {
      this.notificationBadge.style.display = "none";
      this.options.notificationCount = 0;
    }
  }
  updateNotificationCount(count) {
    this.options.notificationCount = count;
    if (count > 0) {
      this.showNotificationBadge(count);
    } else {
      this.hideNotificationBadge();
    }
  }
  setTheme(theme) {
    if (this.element) {
      this.element.setAttribute("data-theme", theme);
    }
  }
  setPosition(position) {
    if (this.element) {
      this.element.setAttribute("data-position", position);
    }
  }
  bounce() {
    if (!this.element) {
      return;
    }
    this.element.style.animation = "tp-bounce 0.6s ease-in-out";
    setTimeout(() => {
      this.element.style.animation = "";
    }, 600);
  }
  hide() {
    if (this.element) {
      this.element.style.display = "none";
    }
  }
  show() {
    if (this.element) {
      this.element.style.display = "";
    }
  }
  // Accessibility
  setAriaLabel(label) {
    this.element.setAttribute("aria-label", label);
  }
  focus() {
    if (this.element) {
      this.element.focus();
    }
  }
  blur() {
    if (this.element) {
      this.element.blur();
    }
  }
  // State management
  disable() {
    if (this.element) {
      this.element.disabled = true;
      this.element.setAttribute("aria-disabled", "true");
    }
  }
  enable() {
    if (this.element) {
      this.element.disabled = false;
      this.element.setAttribute("aria-disabled", "false");
    }
  }
  // Cleanup
  destroy() {
    if (this.element && this.element.parentNode) {
      this.element.parentNode.removeChild(this.element);
    }
  }
  // Alias methods for test compatibility
  showNotification(count = 1) {
    return this.showNotificationBadge(count);
  }
  hideNotification() {
    return this.hideNotificationBadge();
  }
}
class ChatWindow {
  constructor(container, options = {}) {
    this.container = container;
    this.options = __spreadValues({
      onClose: () => {
      },
      onTabSwitch: () => {
      },
      onSendMessage: () => {
      },
      onFileUpload: () => {
      },
      onConversationSelect: () => {
      },
      onBackButtonClick: () => {
      },
      teamLogoIcon: null,
      teamSettings: null
    }, options);
    this.element = null;
    this.activeTab = "home";
    this.conversations = [];
    this.unreadConversations = /* @__PURE__ */ new Set();
    this.currentMessages = [];
    this.isUploading = false;
    this.render();
    this.attachEventListeners();
  }
  render() {
    this.element = createDOMElement("div", {
      className: CSS_CLASSES.WINDOW
    });
    this.element.innerHTML = `
      <div class="ticketping-chat-content">
        <div class="ticketping-tab-content active" id="homeTab">
          <div class="ticketping-chat-header">
            <div class="ticketping-workspace-logo">
              ${this.getLogoHtml()}
            </div>
            <button class="ticketping-close-btn" aria-label="Close chat">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
              </svg>
            </button>
          </div>
          <div class="ticketping-home-container">
            <div class="ticketping-home-content">
              <h4>${this.getWelcomeTitle()}</h4>
              <p>${this.getWelcomeMessage()}</p>
            </div>
            <div class="ticketping-actions-container">
              <div class="ticketping-recent-conversation" id="recentConversationSection" style="display: none;">
                <div class="ticketping-recent-conversation-header">
                  <span class="ticketping-recent-conversation-title">Recent Conversation</span>
                </div>
                <div class="ticketping-recent-conversation-item" id="recentConversationItem">
                  <div class="ticketping-recent-conversation-preview"></div>
                  <div class="ticketping-recent-conversation-time"></div>
                </div>
              </div>
              <button class="ticketping-start-conversation-btn">
                <div class="ticketping-start-conversation-btn-content">
                  <span class="ticketping-start-conversation-btn-text">Send us a message</span>
                  <span class="ticketping-start-conversation-btn-subtext">${this.getResponseTimeText()}</span>
                </div>
                <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 18 18"><path d="M16.345,1.654c-.344-.344-.845-.463-1.305-.315L2.117,5.493c-.491,.158-.831,.574-.887,1.087-.056,.512,.187,.992,.632,1.251l4.576,2.669,3.953-3.954c.293-.293,.768-.293,1.061,0s.293,.768,0,1.061l-3.954,3.954,2.669,4.576c.235,.402,.65,.639,1.107,.639,.048,0,.097-.003,.146-.008,.512-.056,.929-.396,1.086-.886L16.661,2.96h0c.148-.463,.027-.963-.316-1.306Z" fill="currentColor" fill-opacity="0.6"></path></svg>
              </button>
            </div>
          </div>
          <div class="ticketping-plug">
            <a href="https://ticketping.com" target="_blank" rel="noopener noreferrer">
              <span>Powered by</span> <svg width="65.499" height="13" viewBox="0 0 65.499 13" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="Ticketping" style="margin-top: 1px;"><path d="M35.072 9.717q-0.6 0 -0.96 -0.36 -0.347 -0.36 -0.347 -0.96V4.304h-1.813V3.144h1.813V0.971h1.374v2.173h1.96v1.16h-1.96V8.157q0 0.4 0.374 0.4h1.374V9.717zm-6.992 0.186q-1 0 -1.746 -0.413 -0.746 -0.427 -1.173 -1.186 -0.413 -0.773 -0.413 -1.786v-0.16q0 -1.026 0.413 -1.786 0.413 -0.773 1.147 -1.186 0.746 -0.427 1.72 -0.427 0.947 0 1.653 0.427 0.719 0.413 1.12 1.16t0.4 1.746v0.52h-5.054q0.027 0.867 0.573 1.387 0.56 0.506 1.387 0.506 0.773 0 1.16 -0.347 0.4 -0.347 0.613 -0.801l1.133 0.587q-0.186 0.374 -0.547 0.786 -0.347 0.413 -0.92 0.693t-1.467 0.281m-1.921 -4.148h3.64q-0.053 -0.746 -0.534 -1.16 -0.48 -0.427 -1.253 -0.427t-1.266 0.427q-0.48 0.413 -0.587 1.16M18.099 9.717V0.385h1.374v5.32h0.213l2.586 -2.56h1.826l-3.319 3.187 3.427 3.386H22.392l-2.707 -2.733h-0.213v2.733zm-4.837 0.186q-0.947 0 -1.72 -0.4 -0.759 -0.4 -1.213 -1.16 -0.44 -0.759 -0.44 -1.826v-0.173q0 -1.067 0.44 -1.813 0.453 -0.759 1.213 -1.16 0.773 -0.413 1.72 -0.413t1.613 0.347 1.067 0.92q0.413 0.573 0.534 1.266l-1.334 0.281q-0.067 -0.44 -0.281 -0.801t-0.6 -0.573 -0.973 -0.213q-0.573 0 -1.04 0.266 -0.453 0.253 -0.719 0.746 -0.266 0.48 -0.266 1.173v0.12q0 0.693 0.266 1.186t0.719 0.746q0.467 0.253 1.04 0.253 0.867 0 1.321 -0.44 0.453 -0.453 0.573 -1.147l1.334 0.307q-0.16 0.68 -0.573 1.253 -0.4 0.573 -1.067 0.92 -0.666 0.334 -1.613 0.334m-6.464 -0.186V3.144h1.374V9.717zm0.693 -7.466q-0.4 0 -0.68 -0.253 -0.266 -0.266 -0.266 -0.68T6.812 0.65q0.281 -0.266 0.68 -0.266 0.413 0 0.68 0.266 0.266 0.253 0.266 0.666t-0.266 0.68q-0.266 0.253 -0.68 0.253M3.158 9.719q-0.6 0 -0.96 -0.36 -0.347 -0.36 -0.347 -0.96v-4.095H0.038V3.144h1.813V0.971h1.374v2.173h1.96v1.16H3.225V8.157q0 0.4 0.374 0.4H4.972V9.717zm58.588 -6.721q0.768 0 1.356 0.312 0.6 0.3 0.936 0.756v-0.961h1.38v6.72q0 0.912 -0.385 1.621 -0.385 0.719 -1.116 1.128 -0.719 0.407 -1.728 0.407 -1.343 0 -2.232 -0.636 -0.888 -0.624 -1.009 -1.704h1.356q0.156 0.516 0.66 0.828 0.516 0.324 1.224 0.324 0.828 0 1.331 -0.504 0.516 -0.504 0.516 -1.465V8.72q-0.348 0.468 -0.948 0.792 -0.588 0.312 -1.343 0.312 -0.863 0 -1.584 -0.432 -0.707 -0.444 -1.128 -1.224 -0.407 -0.792 -0.407 -1.789t0.407 -1.764q0.42 -0.768 1.128 -1.187 0.719 -0.432 1.584 -0.432m2.292 3.408q0 -0.685 -0.288 -1.187 -0.276 -0.504 -0.732 -0.768t-0.984 -0.264 -0.984 0.264q-0.456 0.251 -0.744 0.756 -0.276 0.492 -0.276 1.175t0.276 1.2q0.288 0.516 0.744 0.792 0.468 0.264 0.984 0.264 0.529 0 0.984 -0.264t0.732 -0.768q0.288 -0.516 0.288 -1.2m-9.654 -3.406q0.78 0 1.392 0.324 0.624 0.324 0.972 0.96t0.348 1.536v3.9h-1.356V6.021q0 -0.888 -0.444 -1.356 -0.444 -0.48 -1.212 -0.48t-1.224 0.48q-0.444 0.468 -0.444 1.356V9.717h-1.368V3.105h1.368v0.756q0.336 -0.407 0.853 -0.636 0.529 -0.229 1.116 -0.229m-13.743 1.081q0.348 -0.456 0.948 -0.768t1.356 -0.312q0.863 0 1.572 0.432 0.719 0.42 1.128 1.187t0.407 1.764 -0.407 1.789q-0.407 0.78 -1.128 1.224 -0.707 0.432 -1.572 0.432 -0.756 0 -1.343 -0.3 -0.588 -0.312 -0.96 -0.768v4.104h-1.368V3.105h1.368zm4.02 2.304q0 -0.685 -0.288 -1.175 -0.276 -0.504 -0.744 -0.756 -0.456 -0.264 -0.984 -0.264 -0.516 0 -0.984 0.264 -0.456 0.264 -0.744 0.768 -0.276 0.504 -0.276 1.187t0.276 1.2q0.288 0.504 0.744 0.768 0.468 0.264 0.984 0.264 0.529 0 0.984 -0.264 0.468 -0.276 0.744 -0.792 0.288 -0.516 0.288 -1.2m2.981 3.337V3.144h1.374V9.717zm0.693 -7.467q-0.4 0 -0.68 -0.253 -0.266 -0.266 -0.266 -0.68t0.266 -0.666q0.281 -0.266 0.68 -0.266 0.413 0 0.68 0.266 0.266 0.253 0.266 0.666t-0.266 0.68q-0.266 0.253 -0.68 0.253" fill="currentColor"/></svg>
            </a>
          </div>
        </div>

        <div class="ticketping-tab-content" id="messagesTab">
          <div class="ticketping-messages-header">
            <button class="ticketping-back-btn" id="tpBackBtn" aria-label="Go back">
              <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M165.66,202.34a8,8,0,0,1-11.32,11.32l-80-80a8,8,0,0,1,0-11.32l80-80a8,8,0,0,1,11.32,11.32L91.31,128Z"></path></svg>
            </button>
            <div class="ticketping-tab-heading">
              <span>Messages</span>
              <span class="ticketping-status-dot ${this.getAvailabilityStatusClass()}"></span>
            </div>
            <button class="ticketping-close-btn-2" aria-label="Close chat">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
              </svg>
            </button>
          </div>
          <div class="ticketping-messages-content">
            <div class="ticketping-conversation-container">
              <div class="ticketping-conversation-list ticketping-thin-scrollbar" id="conversationList">
                <!-- Conversations will be populated here -->
              </div>
              <div class="ticketping-send-a-message-container" id="sendMessageBtnContainer">
                <button class="ticketping-send-message-btn">
                  <span class="ticketping-send-message-btn-text">Send us a message</span>
                  <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 18 18"><path d="M16.345,1.654c-.344-.344-.845-.463-1.305-.315L2.117,5.493c-.491,.158-.831,.574-.887,1.087-.056,.512,.187,.992,.632,1.251l4.576,2.669,3.953-3.954c.293-.293,.768-.293,1.061,0s.293,.768,0,1.061l-3.954,3.954,2.669,4.576c.235,.402,.65,.639,1.107,.639,.048,0,.097-.003,.146-.008,.512-.056,.929-.396,1.086-.886L16.661,2.96h0c.148-.463,.027-.963-.316-1.306Z" fill="currentColor"></path></svg>
                </button>
              </div>
            </div>

            <div class="active-conversation" id="activeConversation" style="display: none;">
              <div class="ticketping-loading-state" id="loadingState" style="display: none;">
                <div class="ticketping-loading-content">
                  <div class="ticketping-loading-spinner">
                    <div class="tp-loading-spinner-child"></div>
                  </div>
                  <div class="ticketping-loading-text">
                    <p>Starting conversation...</p>
                    <p class="ticketping-loading-subtext">Connecting you with support</p>
                  </div>
                </div>
              </div>

              <div class="ticketping-messages-list ticketping-thin-scrollbar" id="messagesList">
                <!-- Messages will be populated here -->
              </div>

              <div class="typing-indicator" id="typingIndicator">
                Support is typing
                <div class="typing-dots">
                  <span></span>
                  <span></span>
                  <span></span>
                </div>
              </div>

              <div class="ticketping-availability-bar" id="availabilityBar">
                ${this.getAvailabilityHtml()}
              </div>

              <div class="ticketping-message-input-container">
                <div class="ticketping-message-input-wrapper">
                  <textarea
                    id="messageInput"
                    class="ticketping-message-input"
                    placeholder="Type your message..."
                    rows="1"
                  ></textarea>
                  <div class="ticketping-input-actions">
                    <div class="ticketping-file-input-container">
                      <input type="file" class="ticketping-file-input" accept="image/*,.pdf,.doc,.docx">
                      <button class="ticketping-file-btn">
                        <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 18 18"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.5 1.75C14.5 1.33579 14.1642 1 13.75 1C13.3358 1 13 1.33579 13 1.75V3.5H11.25C10.8358 3.5 10.5 3.83579 10.5 4.25C10.5 4.66421 10.8358 5 11.25 5H13V6.75C13 7.16421 13.3358 7.5 13.75 7.5C14.1642 7.5 14.5 7.16421 14.5 6.75V5H16.25C16.6642 5 17 4.66421 17 4.25C17 3.83579 16.6642 3.5 16.25 3.5H14.5V1.75Z" fill="currentColor"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 4.75C2 2.67879 3.67879 1 5.75 1C7.82121 1 9.5 2.67879 9.5 4.75V11.75C9.5 12.9922 8.49221 14 7.25 14C6.00779 14 5 12.9922 5 11.75V5C5 4.58579 5.33579 4.25 5.75 4.25C6.16421 4.25 6.5 4.58579 6.5 5V11.75C6.5 12.1638 6.83621 12.5 7.25 12.5C7.66379 12.5 8 12.1638 8 11.75V4.75C8 3.50721 6.99279 2.5 5.75 2.5C4.50721 2.5 3.5 3.50721 3.5 4.75V11.75C3.5 13.8208 5.17921 15.5 7.25 15.5C9.32079 15.5 11 13.8208 11 11.75V8.75C11 8.33579 11.3358 8 11.75 8C12.1642 8 12.5 8.33579 12.5 8.75V11.75C12.5 14.6492 10.1492 17 7.25 17C4.35079 17 2 14.6492 2 11.75V4.75Z" fill="currentColor" fill-opacity="0.4" data-color="color-2"></path></svg>
                      </button>
                    </div>
                    <button class="ticketping-send-btn" disabled>
                      <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256"><path d="M208.49,120.49a12,12,0,0,1-17,0L140,69V216a12,12,0,0,1-24,0V69L64.49,120.49a12,12,0,0,1-17-17l72-72a12,12,0,0,1,17,0l72,72A12,12,0,0,1,208.49,120.49Z"></path></svg>
                    </button>
                  </div>
                </div>
                <div class="ticketping-uploading-overlay" style="display: none;">
                  <div class="ticketping-uploading-content">
                    <div class="ticketping-spinner">
                      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <circle cx="12" cy="12" r="10" stroke="#e5e7eb" stroke-width="2"/>
                        <path d="M12 2a10 10 0 0 1 10 10" stroke="#3b82f6" stroke-width="2" stroke-linecap="round">
                          <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/>
                        </path>
                      </svg>
                    </div>
                    <span class="ticketping-uploading-text">Uploading...</span>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div class="ticketping-chat-tabs" id="ticketpingChatTabs">
        <button class="ticketping-tab active" data-tab="home">
          <div class="tab-icon">
<svg class="icon-inactive" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 18 18"><path d="M14.855 5.95L9.605 1.96C9.247 1.688 8.752 1.688 8.395 1.96L3.145 5.95C2.896 6.139 2.75 6.434 2.75 6.747V14.251C2.75 15.356 3.645 16.251 4.75 16.251H7.25V12.251C7.25 11.699 7.698 11.251 8.25 11.251H9.75C10.302 11.251 10.75 11.699 10.75 12.251V16.251H13.25C14.355 16.251 15.25 15.356 15.25 14.251V6.746C15.25 6.433 15.104 6.14 14.855 5.95Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"></path></svg>
            <svg class="icon-active" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 18 18"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.0591 1.36312C9.4333 0.886573 8.56694 0.887449 7.94127 1.36281L2.69155 5.3526C2.2559 5.68346 2 6.19867 2 6.746V14.25C2 15.7692 3.23079 17 4.75 17H13.25C14.7692 17 16 15.7692 16 14.25V6.746C16 6.20008 15.7448 5.68398 15.3088 5.35288L10.0591 1.36312Z" fill="currentColor" fill-opacity="0.4" data-color="color-2"></path>
<path d="M11.5 13.5V17H6.5V13.5C6.5 12.1193 7.61929 11 9 11C10.3807 11 11.5 12.1193 11.5 13.5Z" fill="currentColor"></path></svg>
          </div>
          <span>Home</span>
        </button>
        <button class="ticketping-tab" data-tab="messages">
          <div class="tab-icon">
            <div class="ticketping-tab-unread-dot" id="messagesTabUnreadDot" style="display: none;"></div>
<svg class="icon-inactive" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 18 18"><path d="M14.25,2.25H3.75c-1.105,0-2,.896-2,2v7c0,1.104,.895,2,2,2h2v3l3.75-3h4.75c1.105,0,2-.896,2-2V4.25c0-1.104-.895-2-2-2Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><line x1="5" y1="6.25" x2="13" y2="6.25" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" data-color="color-2"></line><line x1="5" y1="9.25" x2="10.25" y2="9.25" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" data-color="color-2"></line></svg>
            <svg class="icon-active" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 18 18"><path d="M3.75 1.5C2.23054 1.5 1 2.73203 1 4.25V11.25C1 12.768 2.23054 14 3.75 14H5V16.25C5 16.5383 5.16526 16.8011 5.42511 16.926C5.68496 17.0509 5.99339 17.0158 6.21852 16.8357L9.76309 14H14.25C15.7695 14 17 12.768 17 11.25V4.25C17 2.73203 15.7695 1.5 14.25 1.5H3.75Z" fill="currentColor" fill-opacity="0.4" data-color="color-2"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.25 6.25C4.25 5.83579 4.58579 5.5 5 5.5H13C13.4142 5.5 13.75 5.83579 13.75 6.25C13.75 6.66421 13.4142 7 13 7H5C4.58579 7 4.25 6.66421 4.25 6.25Z" fill="currentColor"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.25 9.25C4.25 8.83579 4.58579 8.5 5 8.5H10.25C10.6642 8.5 11 8.83579 11 9.25C11 9.66421 10.6642 10 10.25 10H5C4.58579 10 4.25 9.66421 4.25 9.25Z" fill="currentColor"></path></svg>

          </div>
          <span>Messages</span>
        </button>
      </div>
    `;
    this.container.appendChild(this.element);
  }
  attachEventListeners() {
    this.element.querySelector(".ticketping-close-btn").addEventListener("click", () => {
      this.options.onClose();
    });
    this.element.querySelector(".ticketping-close-btn-2").addEventListener("click", () => {
      this.options.onClose();
    });
    this.element.querySelector("#tpBackBtn").addEventListener("click", () => {
      this.showConversationList();
      this.options.onBackButtonClick();
    });
    this.element.querySelectorAll(".ticketping-tab").forEach((tab) => {
      tab.addEventListener("click", () => {
        this.switchTab(tab.dataset.tab);
      });
    });
    this.element.querySelector(".ticketping-start-conversation-btn").addEventListener("click", () => {
      this.options.onConversationSelect("new");
      setTimeout(() => {
        this.element.querySelector("#messageInput").focus();
      }, 50);
    });
    this.element.querySelector(".ticketping-send-message-btn").addEventListener("click", () => {
      this.options.onConversationSelect("new");
      setTimeout(() => {
        this.element.querySelector("#messageInput").focus();
      }, 50);
    });
    const recentConversationItem = this.element.querySelector("#recentConversationItem");
    if (recentConversationItem) {
      recentConversationItem.addEventListener("click", () => {
        const sessionId = recentConversationItem.dataset.conversationId;
        if (sessionId) {
          this.options.onConversationSelect(sessionId);
        }
      });
    }
    const messageInput = this.element.querySelector("#messageInput");
    const sendBtn = this.element.querySelector(".ticketping-send-btn");
    messageInput.addEventListener("input", () => {
      this.handleInputChange(messageInput, sendBtn);
    });
    messageInput.addEventListener("keypress", (e) => {
      if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        this.sendMessage(messageInput, sendBtn);
      }
    });
    sendBtn.addEventListener("click", () => {
      this.sendMessage(messageInput, sendBtn);
    });
    const fileBtn = this.element.querySelector(".ticketping-file-btn");
    const fileInput = this.element.querySelector(".ticketping-file-input");
    fileBtn.addEventListener("click", () => fileInput.click());
    fileInput.addEventListener("change", () => this.handleFileUpload(fileInput));
  }
  show() {
    this.element.classList.add(CSS_CLASSES.OPEN);
  }
  hide() {
    this.element.classList.remove(CSS_CLASSES.OPEN);
  }
  switchTab(tabName) {
    this.activeTab = tabName;
    this.element.querySelectorAll(".ticketping-tab").forEach((tab) => {
      tab.classList.toggle("active", tab.dataset.tab === tabName);
    });
    this.element.querySelectorAll(".ticketping-tab-content").forEach((content) => {
      content.classList.toggle("active", content.id === `${tabName}Tab`);
    });
    if (tabName === "messages") {
      this.element.querySelector("#conversationList").classList.add("show");
      this.element.querySelector("#sendMessageBtnContainer").classList.add("show");
    } else {
      this.element.querySelector("#conversationList").classList.remove("show");
      this.element.querySelector("#sendMessageBtnContainer").classList.remove("show");
    }
    this.options.onTabSwitch(tabName);
  }
  setConversations(conversations, unreadConversations = /* @__PURE__ */ new Set()) {
    this.conversations = conversations;
    this.unreadConversations = unreadConversations instanceof Set ? unreadConversations : new Set(unreadConversations);
    this.renderConversationList();
    this.updateRecentConversation();
  }
  renderConversationList() {
    const listElement = this.element.querySelector("#conversationList");
    if (this.conversations.length === 0) {
      this.showEmptyState();
      return;
    }
    listElement.innerHTML = "";
    const sortedConversations = [...this.conversations].sort((a, b) => {
      const dateA = new Date(a.modified || a.created);
      const dateB = new Date(b.modified || b.created);
      return dateB - dateA;
    });
    sortedConversations.forEach((conversation) => {
      var _a;
      const isUnread = this.unreadConversations.has(conversation.sessionId);
      const item = createDOMElement("div", {
        className: `ticketping-conversation-item${isUnread ? " unread" : ""}`,
        "data-conversation": conversation.sessionId
      });
      const lastMessage = (_a = conversation["messages"]) == null ? void 0 : _a[conversation["messages"].length - 1];
      const snippet = lastMessage ? lastMessage.messageText.substring(0, 50) + "..." : "";
      item.innerHTML = `
        ${isUnread ? '<div class="ticketping-unread-dot"></div>' : ""}
        <div class="ticketping-conversation-preview${isUnread ? " unread" : ""}">${conversation.summary || snippet || "Support Chat"}</div>
        <div class="ticketping-conversation-time">${this.formatDateTime(conversation.modified || conversation.created)}</div>
      `;
      item.addEventListener("click", () => {
        this.options.onConversationSelect(conversation.sessionId);
      });
      listElement.appendChild(item);
    });
  }
  showEmptyState() {
    const listElement = this.element.querySelector("#conversationList");
    listElement.innerHTML = `
      <div class="empty-state">
        <div class="empty-state-content">
          <p>No conversations yet</p>
          <p class="empty-state-subtext">Send us a message to get help</p>
        </div>
      </div>
    `;
  }
  showConversationItem() {
    this.element.querySelector("#conversationList").classList.remove("show");
    this.element.querySelector("#activeConversation").style.display = "flex";
    this.element.querySelector("#tpBackBtn").classList.add("show");
    this.element.querySelector("#sendMessageBtnContainer").classList.remove("show");
    this.element.querySelector("#ticketpingChatTabs").style.display = "none";
    this.hideLoadingState();
    setTimeout(() => {
      this.element.querySelector("#messageInput").focus();
    }, 50);
  }
  showConversationList() {
    this.element.querySelector("#conversationList").classList.add("show");
    this.element.querySelector("#activeConversation").style.display = "none";
    this.element.querySelector("#tpBackBtn").classList.remove("show");
    this.element.querySelector("#sendMessageBtnContainer").classList.add("show");
    this.element.querySelector("#ticketpingChatTabs").style.display = "flex";
    this.clearMessages();
    this.hideLoadingState();
  }
  showLoadingState() {
    this.element.querySelector("#conversationList").classList.remove("show");
    this.element.querySelector("#activeConversation").style.display = "flex";
    this.element.querySelector("#loadingState").style.display = "flex";
    this.element.querySelector("#messagesList").style.display = "none";
    this.element.querySelector("#typingIndicator").style.display = "none";
    this.element.querySelector(".ticketping-message-input-container").style.display = "none";
    this.element.querySelector("#tpBackBtn").classList.add("show");
    this.element.querySelector("#sendMessageBtnContainer").classList.remove("show");
    this.element.querySelector("#ticketpingChatTabs").style.display = "none";
  }
  hideLoadingState() {
    this.element.querySelector("#loadingState").style.display = "none";
    this.element.querySelector("#messagesList").style.display = "block";
    this.element.querySelector("#typingIndicator").style.display = "none";
    this.element.querySelector(".ticketping-message-input-container").style.display = "block";
  }
  addMessage(message) {
    const messagesList = this.element.querySelector("#messagesList");
    const previousMessage = this.currentMessages[this.currentMessages.length - 1];
    this.currentMessages.push(message);
    const processedMessage = this.processNewMessageForGrouping(message, previousMessage);
    if (processedMessage.updatePrevious && previousMessage) {
      const previousMessageElements = messagesList.querySelectorAll(".ticketping-message");
      const lastPreviousElement = previousMessageElements[previousMessageElements.length - 1];
      if (lastPreviousElement) {
        this.updateMessageElementForGrouping(lastPreviousElement, previousMessage, false);
      }
    }
    if (processedMessage.showDateSeparator) {
      const dateSeparator = this.createDateSeparatorElement(processedMessage.created);
      messagesList.appendChild(dateSeparator);
    }
    const messageElement = this.createMessageElement(processedMessage);
    messagesList.appendChild(messageElement);
    this.scrollToBottom();
  }
  setMessages(messages) {
    const messagesList = this.element.querySelector("#messagesList");
    messagesList.innerHTML = "";
    this.currentMessages = [...messages];
    const processedMessages = this.processMessagesForGrouping(messages);
    processedMessages.forEach((message) => {
      if (message.showDateSeparator) {
        const dateSeparator = this.createDateSeparatorElement(message.created);
        messagesList.appendChild(dateSeparator);
      }
      const messageElement = this.createMessageElement(message);
      messagesList.appendChild(messageElement);
    });
    this.scrollToBottom();
  }
  clearMessages() {
    const messagesList = this.element.querySelector("#messagesList");
    messagesList.innerHTML = "";
    this.currentMessages = [];
  }
  createMessageElement(message) {
    let cssClasses = `ticketping-message ${message.sender.toLowerCase()}`;
    if (message.isGrouped) {
      cssClasses += " grouped";
    }
    if (message.isFirstInGroup) {
      cssClasses += " first-in-group";
    }
    if (message.isLastInGroup) {
      cssClasses += " last-in-group";
    }
    const element = createDOMElement("div", {
      className: cssClasses
    });
    const hasAttachment = message.filename && message.filepath;
    const showTimestamp = message.showTimestamp !== false;
    if (hasAttachment) {
      const messageContent = message.messageHtml ? message.messageHtml : this.escapeHtml(message.messageText || "");
      const attachmentHtml = this.createAttachmentHtml(message.filename, message.filepath);
      element.innerHTML = `
        <div class="ticketping-message-bubble">
          ${messageContent}
          ${attachmentHtml}
        </div>
        ${showTimestamp ? `<div class="ticketping-message-time">${this.formatTime(message.created)}</div>` : ""}
      `;
    } else {
      element.innerHTML = `
        <div class="ticketping-message-bubble">${message.messageHtml ? message.messageHtml : this.escapeHtml(message.messageText)}</div>
        ${showTimestamp ? `<div class="ticketping-message-time">${this.formatTime(message.created)}</div>` : ""}
      `;
    }
    return element;
  }
  createDateSeparatorElement(date) {
    const element = createDOMElement("div", {
      className: "ticketping-date-separator"
    });
    element.innerHTML = `
      <div class="ticketping-date-separator-line"></div>
      <div class="ticketping-date-separator-text">${this.formatDateSeparator(date)}</div>
      <div class="ticketping-date-separator-line"></div>
    `;
    return element;
  }
  processMessagesForGrouping(messages) {
    if (!messages || messages.length === 0) {
      return messages;
    }
    if (messages.length === 1) {
      return [__spreadProps(__spreadValues({}, messages[0]), { showTimestamp: true })];
    }
    const TIME_THRESHOLD = 5 * 60 * 1e3;
    const processedMessages = messages.map((message) => __spreadProps(__spreadValues({}, message), { showTimestamp: false, showDateSeparator: false }));
    const groups = [];
    let currentGroup = { start: 0, end: 0, sender: processedMessages[0].sender };
    for (let i = 1; i < processedMessages.length; i++) {
      const current = processedMessages[i];
      const previous = processedMessages[i - 1];
      const currentDate = this.getDateInUserTimezone(current.created);
      const previousDate = this.getDateInUserTimezone(previous.created);
      if (currentDate !== previousDate) {
        processedMessages[i].showDateSeparator = true;
      }
      const currentTime = new Date(current.created).getTime();
      const previousTime = new Date(previous.created).getTime();
      const timeGap = currentTime - previousTime;
      const senderChanged = current.sender !== previous.sender;
      const timeGapTooLarge = timeGap > TIME_THRESHOLD;
      if (senderChanged || timeGapTooLarge) {
        currentGroup.end = i - 1;
        groups.push(currentGroup);
        currentGroup = { start: i, end: i, sender: current.sender };
      } else {
        currentGroup.end = i;
      }
    }
    groups.push(currentGroup);
    groups.forEach((group) => {
      processedMessages[group.end].showTimestamp = true;
      if (group.start === group.end) {
        processedMessages[group.start].showTimestamp = true;
      } else {
        for (let i = group.start; i <= group.end; i++) {
          processedMessages[i].isGrouped = true;
          processedMessages[i].isFirstInGroup = i === group.start;
          processedMessages[i].isLastInGroup = i === group.end;
        }
      }
    });
    processedMessages[0].showTimestamp = true;
    return processedMessages;
  }
  processNewMessageForGrouping(newMessage, previousMessage) {
    const TIME_THRESHOLD = 5 * 60 * 1e3;
    const processedMessage = __spreadProps(__spreadValues({}, newMessage), {
      showTimestamp: true,
      // Default to showing timestamp
      showDateSeparator: false,
      isGrouped: false,
      isFirstInGroup: false,
      isLastInGroup: false,
      updatePrevious: false
    });
    if (!previousMessage) {
      return processedMessage;
    }
    const currentDate = this.getDateInUserTimezone(newMessage.created);
    const previousDate = this.getDateInUserTimezone(previousMessage.created);
    if (currentDate !== previousDate) {
      processedMessage.showDateSeparator = true;
    }
    const currentTime = new Date(newMessage.created).getTime();
    const previousTime = new Date(previousMessage.created).getTime();
    const timeGap = currentTime - previousTime;
    const senderChanged = newMessage.sender !== previousMessage.sender;
    const timeGapTooLarge = timeGap > TIME_THRESHOLD;
    if (!senderChanged && !timeGapTooLarge) {
      processedMessage.isGrouped = true;
      processedMessage.isLastInGroup = true;
      processedMessage.showTimestamp = true;
      processedMessage.updatePrevious = true;
    }
    return processedMessage;
  }
  updateMessageElementForGrouping(messageElement, message, showTimestamp) {
    messageElement.classList.add("grouped");
    messageElement.classList.remove("last-in-group");
    const timeElement = messageElement.querySelector(".ticketping-message-time");
    if (timeElement) {
      timeElement.style.display = showTimestamp ? "block" : "none";
    }
  }
  handleInputChange(input, sendBtn) {
    const hasText = input.value.trim().length > 0;
    sendBtn.disabled = !hasText;
    this.toggleFileInputButton(!hasText);
    this.autoResizeTextarea(input);
  }
  sendMessage(input, sendBtn) {
    const text = input.value.trim();
    if (!text) {
      return;
    }
    this.options.onSendMessage({ text });
    input.value = "";
    sendBtn.disabled = true;
    this.toggleFileInputButton(true);
    this.autoResizeTextarea(input);
  }
  toggleFileInputButton(show) {
    const fileInputContainer = this.element.querySelector(".ticketping-file-input-container");
    if (fileInputContainer) {
      fileInputContainer.classList.toggle("tp-hidden", !show);
    }
  }
  handleFileUpload(fileInput) {
    return __async(this, null, function* () {
      const file = fileInput.files[0];
      if (file) {
        this.setUploadingState(true);
        yield this.options.onFileUpload(file);
        fileInput.value = "";
        this.finishFileUpload();
      }
    });
  }
  setUploadingState(isUploading) {
    this.isUploading = isUploading;
    const uploadingOverlay = this.element.querySelector(".ticketping-uploading-overlay");
    if (isUploading) {
      uploadingOverlay.style.display = "flex";
    } else {
      uploadingOverlay.style.display = "none";
    }
  }
  finishFileUpload() {
    this.setUploadingState(false);
  }
  showTypingIndicator(show = true) {
    const indicator = this.element.querySelector("#typingIndicator");
    indicator.classList.toggle("show", show);
    if (show) {
      this.scrollToBottom();
    }
  }
  autoResizeTextarea(textarea) {
    textarea.style.height = "auto";
    textarea.style.height = Math.min(textarea.scrollHeight, 100) + "px";
  }
  scrollToBottom() {
    const messagesList = this.element.querySelector("#messagesList");
    setTimeout(() => {
      messagesList.scrollTop = messagesList.scrollHeight;
    }, 100);
  }
  formatTime(date) {
    return new Date(date).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
  }
  formatDateTime(date) {
    return new Date(date).toLocaleString([], { month: "long", day: "numeric", hour: "numeric", minute: "2-digit" });
  }
  getDateInUserTimezone(date) {
    return new Date(date).toLocaleDateString();
  }
  formatDateSeparator(date) {
    const messageDate = new Date(date);
    const today = /* @__PURE__ */ new Date();
    const yesterday = new Date(today);
    yesterday.setDate(yesterday.getDate() - 1);
    const messageDateStr = this.getDateInUserTimezone(date);
    const todayStr = this.getDateInUserTimezone(today);
    const yesterdayStr = this.getDateInUserTimezone(yesterday);
    if (messageDateStr === todayStr) {
      return "Today";
    } else if (messageDateStr === yesterdayStr) {
      return "Yesterday";
    } else {
      return messageDate.toLocaleDateString([], {
        weekday: "long",
        month: "long",
        day: "numeric",
        year: messageDate.getFullYear() !== today.getFullYear() ? "numeric" : void 0
      });
    }
  }
  escapeHtml(text) {
    const div = document.createElement("div");
    div.textContent = text;
    return div.innerHTML;
  }
  getLogoHtml() {
    const teamLogoIcon = this.options.teamLogoIcon;
    if (teamLogoIcon) {
      return `<img src="${teamLogoIcon}" alt="logo">`;
    }
    return '<svg width="40" height="40" viewBox="0 0 1.2 1.2" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.1 0.25a0.15 0.15 0 1 1 -0.3 0 0.15 0.15 0 0 1 0.3 0" fill="#4CB782"/><path opacity=".5" d="M0.762 0.127A0.5 0.5 0 0 0 0.6 0.1C0.324 0.1 0.1 0.324 0.1 0.6c0 0.08 0.019 0.156 0.052 0.223 0.009 0.018 0.012 0.038 0.007 0.057l-0.03 0.111a0.065 0.065 0 0 0 0.08 0.08l0.111 -0.03a0.082 0.082 0 0 1 0.057 0.007A0.498 0.498 0 0 0 0.6 1.1c0.276 0 0.5 -0.224 0.5 -0.5 0 -0.057 -0.009 -0.111 -0.027 -0.162a0.225 0.225 0 0 1 -0.312 -0.312" fill="#1C274C"/></svg>';
  }
  getWelcomeTitle() {
    return "Hi there 👋";
  }
  getWelcomeMessage() {
    const teamSettings = this.options.teamSettings;
    if (teamSettings == null ? void 0 : teamSettings.widgetWelcomeMessage) {
      return this.escapeHtml(teamSettings.widgetWelcomeMessage);
    }
    return "How can we help you?";
  }
  getAvailabilityHtml() {
    const teamSettings = this.options.teamSettings;
    if (!teamSettings) {
      return "";
    }
    const { isAvailable, nextAvailable, workHoursDisplay } = teamSettings;
    if (isAvailable) {
      return "";
    }
    const moonIcon = '<svg class="ticketping-availability-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 18 18"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.54419 1.47446C8.70875 1.73227 8.70028 2.06417 8.52278 2.31324C7.88003 3.21522 7.5 4.31129 7.5 5.49999C7.5 8.53778 9.96222 11 13 11C14.0509 11 15.029 10.7009 15.8667 10.1868C16.1275 10.0267 16.4594 10.0412 16.7053 10.2233C16.9513 10.4054 17.0619 10.7186 16.9848 11.0148C16.0904 14.4535 12.9735 17 9.25 17C4.83179 17 1.25 13.4182 1.25 8.99999C1.25 5.08453 4.06262 1.83365 7.77437 1.14073C8.07502 1.0846 8.37963 1.21666 8.54419 1.47446Z" fill="currentColor"></path></svg>';
    const subtitle = `<div class="ticketping-availability-subtitle">Leave a message and we'll pick it up first thing.</div>`;
    if (nextAvailable) {
      return `
        <div class="ticketping-availability ticketping-availability--offline">
          ${moonIcon}
          <div class="ticketping-availability-content">
            <div class="ticketping-availability-title">Offline right now — back ${nextAvailable.dayName} at ${nextAvailable.timeFormatted} ${nextAvailable.timezoneAbbr}</div>
            ${subtitle}
          </div>
        </div>
      `;
    }
    if (workHoursDisplay == null ? void 0 : workHoursDisplay.full) {
      return `
        <div class="ticketping-availability ticketping-availability--offline">
          ${moonIcon}
          <div class="ticketping-availability-content">
            <div class="ticketping-availability-title">Offline right now — Hours: ${this.escapeHtml(workHoursDisplay.full)}</div>
            ${subtitle}
          </div>
        </div>
      `;
    }
    return `
      <div class="ticketping-availability ticketping-availability--offline">
        ${moonIcon}
        <div class="ticketping-availability-content">
          <div class="ticketping-availability-title">Offline right now</div>
          ${subtitle}
        </div>
      </div>
    `;
  }
  getAvailabilityStatusClass() {
    const teamSettings = this.options.teamSettings;
    if (!teamSettings) {
      return "online";
    }
    return teamSettings.isAvailable ? "online" : "offline";
  }
  getResponseTimeText() {
    const teamSettings = this.options.teamSettings;
    if (!teamSettings) {
      return "Typically replies within minutes";
    }
    if (teamSettings.isAvailable) {
      return "Typically replies within minutes";
    }
    if (teamSettings.nextAvailable) {
      return `We'll respond on ${teamSettings.nextAvailable.dayName} at ${teamSettings.nextAvailable.timeFormatted}`;
    }
    return "Typically replies within minutes";
  }
  isImageFile(filename) {
    const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg"];
    const extension = filename.toLowerCase().substring(filename.lastIndexOf("."));
    return imageExtensions.includes(extension);
  }
  createAttachmentHtml(filename, filepath) {
    const escapedFilename = this.escapeHtml(filename);
    const isImage = this.isImageFile(filename);
    if (isImage) {
      return `
        <div class="ticketping-message-attachment">
          <div class="ticketping-attachment-image">
            <a href="${filepath}" target="_blank" rel="noopener noreferrer">
              <div class="ticketping-image-container">
                <div class="ticketping-image-placeholder"></div>
                <img src="${filepath}" alt="${escapedFilename}" class="ticketping-attachment-img" onload="this.parentElement.classList.add('loaded')" onerror="this.parentElement.classList.add('error')" />
              </div>
            </a>
          </div>
        </div>
      `;
    } else {
      return `
        <div class="ticketping-message-attachment">
          <div class="ticketping-attachment-info">
            <div class="ticketping-attachment-name">
              <a href="${filepath}" target="_blank" rel="noopener noreferrer" style="color: #3B82F6; text-decoration: underline;">
                ${escapedFilename}
              </a>
            </div>
          </div>
        </div>
      `;
    }
  }
  updateAgentStatus(status) {
    const headerSubtext = this.element.querySelector(".ticketping-chat-header-content p");
    if (status === "online") {
      headerSubtext.textContent = "We're online and ready to help!";
    } else {
      headerSubtext.textContent = "We'll get back to you soon!";
    }
  }
  updateRecentConversation() {
    var _a;
    const recentConversationSection = this.element.querySelector("#recentConversationSection");
    const recentConversationItem = this.element.querySelector("#recentConversationItem");
    if (!recentConversationSection || !recentConversationItem) {
      return;
    }
    if (this.conversations.length === 0) {
      recentConversationSection.style.display = "none";
      return;
    }
    const sortedConversations = [...this.conversations].sort((a, b) => {
      const dateA = new Date(a.modified || a.created);
      const dateB = new Date(b.modified || b.created);
      return dateB - dateA;
    });
    const recentConversation = sortedConversations[0];
    const isUnread = this.unreadConversations.has(recentConversation.sessionId);
    const lastMessage = (_a = recentConversation["messages"]) == null ? void 0 : _a[recentConversation["messages"].length - 1];
    const snippet = lastMessage ? lastMessage.messageText.substring(0, 60) + "..." : "";
    const preview = recentConversation.summary || snippet || "Support Chat";
    const previewElement = recentConversationItem.querySelector(".ticketping-recent-conversation-preview");
    previewElement.textContent = preview;
    if (isUnread) {
      recentConversationItem.classList.add("unread");
      previewElement.classList.add("unread");
      if (!recentConversationItem.querySelector(".ticketping-unread-dot")) {
        const dot = createDOMElement("div", { className: "ticketping-unread-dot" });
        recentConversationItem.insertBefore(dot, recentConversationItem.firstChild);
      }
    } else {
      recentConversationItem.classList.remove("unread");
      previewElement.classList.remove("unread");
      const existingDot = recentConversationItem.querySelector(".ticketping-unread-dot");
      if (existingDot) {
        existingDot.remove();
      }
    }
    recentConversationItem.querySelector(".ticketping-recent-conversation-time").textContent = this.formatDateTime(recentConversation.modified || recentConversation.created);
    recentConversationItem.dataset.conversationId = recentConversation.sessionId;
    recentConversationSection.style.display = "block";
  }
  /**
   * Show or hide the unread dot on the Messages tab
   */
  setMessagesTabUnread(hasUnread) {
    const dot = this.element.querySelector("#messagesTabUnreadDot");
    if (dot) {
      dot.style.display = hasUnread ? "block" : "none";
    }
  }
  showError(message) {
    console.error(message);
  }
  destroy() {
    if (this.element && this.element.parentNode) {
      this.element.parentNode.removeChild(this.element);
    }
  }
}
class WebSocketService {
  constructor(wsUrl, token = null, options = {}) {
    this.wsUrl = wsUrl;
    this.token = token;
    this.isAnonymous = !token;
    this.options = __spreadValues({
      onSessionState: () => {
      },
      onMessage: () => {
      },
      onFileAttachment: () => {
      },
      onMessageHistory: () => {
      },
      onTyping: () => {
      },
      onStatusChange: () => {
      },
      onError: () => {
      },
      onConnect: () => {
      },
      onDisconnect: () => {
      },
      reconnectAttempts: 5,
      reconnectDelay: 1e3,
      heartbeatInterval: 3e4
    }, options);
    this.ws = null;
    this.isConnected = false;
    this.reconnectCount = 0;
    this.heartbeatTimer = null;
    this.reconnectTimer = null;
    this.typingTimer = null;
    this.connect();
  }
  connect() {
    try {
      this.ws = new WebSocket(this.wsUrl);
      this.attachEventListeners();
    } catch (error) {
      console.error("WebSocket connection failed:", error);
      this.options.onError(error);
      this.scheduleReconnect();
    }
  }
  attachEventListeners() {
    this.ws.onopen = (event) => {
      console.log("WebSocket connected");
      this.isConnected = true;
      this.reconnectCount = 0;
      this.authenticate();
      this.startHeartbeat();
      this.options.onConnect(event);
    };
    this.ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        this.handleMessage(data);
      } catch (error) {
        console.error("Failed to parse WebSocket message:", error);
        this.options.onError(error);
      }
    };
    this.ws.onclose = (event) => {
      console.log("WebSocket disconnected:", event.code, event.reason);
      this.isConnected = false;
      this.stopHeartbeat();
      this.options.onDisconnect(event);
      if (event.code !== 1e3 && this.reconnectCount < this.options.reconnectAttempts) {
        this.scheduleReconnect();
      }
    };
    this.ws.onerror = (error) => {
      console.error("WebSocket error:", error);
      this.options.onError(error);
    };
  }
  authenticate() {
    if (this.isAnonymous) {
      this.send({
        type: WEBSOCKET_EVENTS.AUTH,
        anonymous: true
      });
    } else {
      this.send({
        type: WEBSOCKET_EVENTS.AUTH,
        token: this.token
      });
    }
  }
  handleMessage(data) {
    switch (data.type) {
      case WEBSOCKET_EVENTS.SERVER_STATE:
        this.options.onSessionState(data);
        break;
      case WEBSOCKET_EVENTS.SERVER_MESSAGE:
        this.options.onMessage(data);
        break;
      case WEBSOCKET_EVENTS.FILE_ATTACHMENT:
        this.options.onFileAttachment(data);
        break;
      case WEBSOCKET_EVENTS.SERVER_MESSAGE_HISTORY:
        this.options.onMessageHistory(data);
        break;
      case WEBSOCKET_EVENTS.SERVER_TYPING:
        this.options.onTyping(data);
        break;
      case WEBSOCKET_EVENTS.SERVER_AGENT_STATUS:
        this.options.onStatusChange(data);
        break;
      case WEBSOCKET_EVENTS.SERVER_AGENT_JOINED:
        this.options.onStatusChange(__spreadValues({ type: "agent_joined" }, data));
        break;
      case WEBSOCKET_EVENTS.SERVER_AGENT_LEFT:
        this.options.onStatusChange(__spreadValues({ type: "agent_left" }, data));
        break;
      case WEBSOCKET_EVENTS.SERVER_CONVERSATION_UPDATED:
        this.options.onMessage(data);
        break;
      case WEBSOCKET_EVENTS.SERVER_AUTH_SUCCESS:
        console.log("WebSocket authentication successful");
        break;
      case WEBSOCKET_EVENTS.SERVER_AUTH_FAILED:
        console.error("WebSocket authentication failed");
        this.options.onError(new Error("Authentication failed"));
        this.disconnect();
        break;
      case WEBSOCKET_EVENTS.SERVER_ANONYMOUS_AUTH_SUCCESS:
        console.log("Anonymous WebSocket authentication successful");
        break;
      case WEBSOCKET_EVENTS.SERVER_ANONYMOUS_AUTH_FAILED:
        console.error("Anonymous WebSocket authentication failed");
        this.options.onError(new Error("Anonymous authentication failed"));
        this.disconnect();
        break;
      case WEBSOCKET_EVENTS.PONG:
        break;
      case WEBSOCKET_EVENTS.ERROR:
        console.error("WebSocket error:", data.message);
        this.options.onError(new Error(data.message));
        break;
      default:
        console.warn("Unknown WebSocket message type:", data.type);
    }
  }
  send(data) {
    if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
      try {
        this.ws.send(JSON.stringify(data));
        return true;
      } catch (error) {
        console.error("Failed to send WebSocket message:", error);
        this.options.onError(error);
        return false;
      }
    } else {
      console.warn("WebSocket not connected, message queued");
      return false;
    }
  }
  sendMessage(message) {
    return this.send(__spreadValues(__spreadValues({}, message), message.file && { file: message.file }));
  }
  sendTypingStart(conversationId) {
    if (this.typingTimer) {
      clearTimeout(this.typingTimer);
    }
    this.send({
      type: WEBSOCKET_EVENTS.TYPING_START,
      conversationId
    });
    this.typingTimer = setTimeout(() => {
      this.sendTypingStop(conversationId);
    }, 3e3);
  }
  sendTypingStop(conversationId) {
    if (this.typingTimer) {
      clearTimeout(this.typingTimer);
      this.typingTimer = null;
    }
    this.send({
      type: WEBSOCKET_EVENTS.TYPING_STOP,
      conversationId
    });
  }
  joinConversation(conversationId) {
    return this.send({
      type: WEBSOCKET_EVENTS.JOIN_CONVERSATION,
      conversationId
    });
  }
  leaveConversation(conversationId) {
    return this.send({
      type: WEBSOCKET_EVENTS.LEAVE_CONVERSATION,
      conversationId
    });
  }
  markRead(sessionId) {
    return this.send({
      type: WEBSOCKET_EVENTS.MARK_READ,
      sessionId
    });
  }
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.isConnected) {
        this.send({ type: "ping" });
      }
    }, this.options.heartbeatInterval);
  }
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }
  scheduleReconnect() {
    if (this.reconnectCount >= this.options.reconnectAttempts) {
      console.error("Max reconnection attempts reached");
      this.options.onError(new Error("Max reconnection attempts reached"));
      return;
    }
    this.reconnectCount++;
    const delay = this.options.reconnectDelay * Math.pow(2, this.reconnectCount - 1);
    console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectCount})`);
    this.reconnectTimer = setTimeout(() => {
      console.log(`Reconnection attempt ${this.reconnectCount}`);
      this.connect();
    }, delay);
  }
  reconnect() {
    this.disconnect();
    this.reconnectCount = 0;
    this.connect();
  }
  disconnect() {
    this.isConnected = false;
    this.stopHeartbeat();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (this.typingTimer) {
      clearTimeout(this.typingTimer);
      this.typingTimer = null;
    }
    if (this.ws) {
      this.ws.close(1e3, "Client disconnect");
      this.ws = null;
    }
  }
  // Utility methods
  getConnectionState() {
    if (!this.ws) {
      return "DISCONNECTED";
    }
    switch (this.ws.readyState) {
      case WebSocket.CONNECTING:
        return "CONNECTING";
      case WebSocket.OPEN:
        return "CONNECTED";
      case WebSocket.CLOSING:
        return "CLOSING";
      case WebSocket.CLOSED:
        return "DISCONNECTED";
      default:
        return "UNKNOWN";
    }
  }
  isAnonymousUser() {
    return this.isAnonymous;
  }
  isWsConnected() {
    return this.isConnected && this.ws && this.ws.readyState === WebSocket.OPEN;
  }
  // Event subscription for external components
  on(eventType, callback) {
    switch (eventType) {
      case "message":
        this.options.onMessage = callback;
        break;
      case "typing":
        this.options.onTyping = callback;
        break;
      case "status":
        this.options.onStatusChange = callback;
        break;
      case "error":
        this.options.onError = callback;
        break;
      case "connect":
        this.options.onConnect = callback;
        break;
      case "disconnect":
        this.options.onDisconnect = callback;
        break;
      default:
        console.warn("Unknown event type:", eventType);
    }
  }
  // Cleanup
  destroy() {
    this.disconnect();
  }
}
class NotificationWS {
  constructor(wsUrl, options = {}) {
    this.wsUrl = wsUrl;
    this.options = __spreadValues({
      onUnreadCount: () => {
      },
      onConnect: () => {
      },
      onDisconnect: () => {
      },
      onError: () => {
      },
      reconnectAttempts: 10,
      reconnectDelay: 2e3,
      heartbeatInterval: 3e4
    }, options);
    this.ws = null;
    this.isConnected = false;
    this.reconnectCount = 0;
    this.heartbeatTimer = null;
    this.reconnectTimer = null;
    this.shouldReconnect = true;
    this.connect();
  }
  connect() {
    if (!this.shouldReconnect) {
      return;
    }
    try {
      this.ws = new WebSocket(this.wsUrl);
      this.attachEventListeners();
    } catch (error) {
      console.error("Notification WebSocket connection failed:", error);
      this.options.onError(error);
      this.scheduleReconnect();
    }
  }
  attachEventListeners() {
    this.ws.onopen = () => {
      console.log("Notification WebSocket connected");
      this.isConnected = true;
      this.reconnectCount = 0;
      this.startHeartbeat();
      this.requestUnreadCount();
      this.options.onConnect();
    };
    this.ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        this.handleMessage(data);
      } catch (error) {
        console.error("Failed to parse notification message:", error);
      }
    };
    this.ws.onclose = (event) => {
      console.log("Notification WebSocket disconnected:", event.code, event.reason);
      this.isConnected = false;
      this.stopHeartbeat();
      this.options.onDisconnect(event);
      console.log(event);
      const isNetworkError = event.code !== 1e3 && event.code !== 1006;
      if (this.shouldReconnect && isNetworkError) {
        this.scheduleReconnect();
      }
    };
    this.ws.onerror = (error) => {
      console.error("Notification WebSocket error:", error);
      this.options.onError(error);
    };
  }
  handleMessage(data) {
    switch (data.type) {
      case "unread_count":
        this.options.onUnreadCount(data.count);
        break;
      case "pong":
        break;
      default:
        console.log("Unknown notification message type:", data.type);
    }
  }
  send(data) {
    if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
      try {
        this.ws.send(JSON.stringify(data));
        return true;
      } catch (error) {
        console.error("Failed to send notification message:", error);
        return false;
      }
    }
    return false;
  }
  requestUnreadCount() {
    return this.send({ type: "get_unread_count" });
  }
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.isConnected) {
        this.send({ type: "ping" });
      }
    }, this.options.heartbeatInterval);
  }
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }
  scheduleReconnect() {
    if (!this.shouldReconnect) {
      return;
    }
    if (this.reconnectCount >= this.options.reconnectAttempts) {
      console.error("Notification WebSocket: Max reconnection attempts reached");
      return;
    }
    this.reconnectCount++;
    const delay = this.options.reconnectDelay * Math.pow(1.5, this.reconnectCount - 1);
    console.log(`Notification WebSocket: Reconnecting in ${delay}ms (attempt ${this.reconnectCount})`);
    this.reconnectTimer = setTimeout(() => {
      this.connect();
    }, delay);
  }
  disconnect() {
    this.shouldReconnect = false;
    this.isConnected = false;
    this.stopHeartbeat();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (this.ws) {
      this.ws.close(1e3, "Client disconnect");
      this.ws = null;
    }
  }
  isWsConnected() {
    return this.isConnected && this.ws && this.ws.readyState === WebSocket.OPEN;
  }
  destroy() {
    this.disconnect();
  }
}
const TP_CHAT_JWT_COOKIE_KEY = "ticketping_chat_jwt";
class ApiService {
  constructor(config) {
    this.config = config;
    this.baseURL = config.apiBase;
  }
  // Cookie utility methods
  setCookie(name, value, hours) {
    const date = /* @__PURE__ */ new Date();
    date.setTime(date.getTime() + hours * 60 * 60 * 1e3);
    const expires = `expires=${date.toUTCString()}`;
    document.cookie = `${name}=${value};${expires};path=/;SameSite=Strict`;
  }
  getCookie(name) {
    const nameEQ = name + "=";
    const ca = document.cookie.split(";");
    for (let i = 0; i < ca.length; i++) {
      let c = ca[i];
      while (c.charAt(0) === " ") {
        c = c.substring(1, c.length);
      }
      if (c.indexOf(nameEQ) === 0) {
        return c.substring(nameEQ.length, c.length);
      }
    }
    return null;
  }
  deleteCookie(name) {
    document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;SameSite=Strict`;
  }
  request(_0) {
    return __async(this, arguments, function* (endpoint, options = {}) {
      const url = `${this.baseURL}${endpoint}`;
      const headers = __spreadValues({
        "Content-Type": "application/json"
      }, options.headers);
      if (this.config.appId) {
        headers["x-tpwidget-id"] = this.config.appId;
      }
      const requestOptions = __spreadValues({
        method: "GET",
        headers
      }, options);
      try {
        const response = yield fetch(url, requestOptions);
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        const contentType = response.headers.get("content-type");
        if (contentType && contentType.includes("application/json")) {
          return yield response.json();
        } else {
          return yield response.text();
        }
      } catch (error) {
        console.error(`API request failed: ${endpoint}`, error);
        if (this.isAuthError(error)) {
          this.clearChatToken();
        }
        throw error;
      }
    });
  }
  // Authentication
  getChatToken() {
    return __async(this, null, function* () {
      const cachedJWT = this.getCookie(TP_CHAT_JWT_COOKIE_KEY);
      if (cachedJWT) {
        try {
          const payload = JSON.parse(atob(cachedJWT.split(".")[1]));
          const currentTime = Math.floor(Date.now() / 1e3);
          if (payload.exp && payload.exp > currentTime) {
            return {
              chatJWT: cachedJWT
            };
          }
        } catch (error) {
          console.log("error", error);
          console.warn("Invalid cached JWT format, fetching new token");
          this.deleteCookie(TP_CHAT_JWT_COOKIE_KEY);
        }
      }
      const params = new URLSearchParams({
        jwt: this.config.userJWT,
        team: this.config.teamSlug
      });
      const response = yield this.request(`${API_ENDPOINTS.auth}?${params}`, {
        method: "GET"
      });
      if (response.jwt) {
        this.setCookie(TP_CHAT_JWT_COOKIE_KEY, response.jwt, 168);
      }
      return {
        chatJWT: response.jwt
      };
    });
  }
  // Clear cached chat token (useful when token becomes invalid)
  clearChatToken() {
    this.deleteCookie(TP_CHAT_JWT_COOKIE_KEY);
  }
  createChatSession() {
    return __async(this, null, function* () {
      const headers = {
        "Content-Type": "application/json"
      };
      const { chatJWT } = yield this.getChatToken();
      return yield this.request(API_ENDPOINTS.newChatSession, {
        method: "POST",
        headers,
        body: JSON.stringify({
          appId: this.config.appId,
          team: this.config.teamSlug,
          jwt: chatJWT
        })
      });
    });
  }
  // Conversations
  getConversations(limit = 50, offset = 0) {
    return __async(this, null, function* () {
      const { chatJWT } = yield this.getChatToken();
      const headers = {
        "Authorization": `Bearer ${chatJWT}`,
        "Content-Type": "application/json"
      };
      const params = new URLSearchParams({
        limit: limit.toString(),
        offset: offset.toString()
      });
      return yield this.request(`${API_ENDPOINTS.conversations}?${params}`, { headers });
    });
  }
  // Messages
  getMessages(conversationId, limit = 50, before = null) {
    return __async(this, null, function* () {
      const params = new URLSearchParams({
        conversationId,
        limit: limit.toString()
      });
      if (before) {
        params.append("before", before);
      }
      return yield this.request(`${API_ENDPOINTS.messages}?${params}`);
    });
  }
  sendMessage(message) {
    return __async(this, null, function* () {
      return yield this.request(API_ENDPOINTS.messages, {
        method: "POST",
        body: JSON.stringify(__spreadValues({
          conversationId: message.conversationId,
          text: message.text,
          type: message.type || "user",
          timestamp: message.timestamp,
          messageId: message.id
        }, message.file && { file: message.file }))
      });
    });
  }
  markAsRead(conversationId, messageId) {
    return __async(this, null, function* () {
      return yield this.request(`${API_ENDPOINTS.messages}/${messageId}/read`, {
        method: "POST",
        body: JSON.stringify({ conversationId })
      });
    });
  }
  // File upload
  uploadFile(file, sessionId) {
    return __async(this, null, function* () {
      const formData = new FormData();
      formData.append("file", file);
      const headers = {};
      if (this.config.userJWT) {
        const { chatJWT } = yield this.getChatToken();
        if (chatJWT) {
          headers["Authorization"] = `Bearer ${chatJWT}`;
        }
      }
      if (this.config.appId) {
        headers["x-tpwidget-id"] = this.config.appId;
      }
      try {
        const response = yield fetch(`${this.baseURL}${API_ENDPOINTS.fileUpload}${this.config.teamSlug}/${sessionId}/`, {
          method: "POST",
          headers,
          body: formData
        });
        if (!response.ok) {
          throw new Error(`Upload failed: ${response.statusText}`);
        }
        const result = yield response.json();
        return result.url;
      } catch (error) {
        console.error("File upload failed:", error);
        throw error;
      }
    });
  }
  // Analytics
  track(_0) {
    return __async(this, arguments, function* (event, data = {}) {
      return;
    });
  }
  updateUser(updates) {
    return __async(this, null, function* () {
      return yield this.request("/users/me", {
        method: "PATCH",
        body: JSON.stringify(updates)
      });
    });
  }
  // Agent status
  getAgentStatus() {
    return __async(this, null, function* () {
      return yield this.request("/agents/status");
    });
  }
  // Help articles (if integrated)
  getHelpArticles(query = "", limit = 5) {
    return __async(this, null, function* () {
      const params = new URLSearchParams({
        q: query,
        limit: limit.toString(),
        appId: this.config.appId
      });
      return yield this.request(`/help/articles?${params}`);
    });
  }
  searchHelpArticles(query) {
    return __async(this, null, function* () {
      return yield this.request("/help/search", {
        method: "POST",
        body: JSON.stringify({
          query,
          appId: this.config.appId
        })
      });
    });
  }
  // Utility methods
  setToken(token) {
    this.token = token;
  }
  setAppId(appId) {
    this.config.appId = appId;
  }
  // Error handling helpers
  isNetworkError(error) {
    return error instanceof TypeError && error.message.includes("fetch");
  }
  isAuthError(error) {
    return error.message.includes("401") || error.message.includes("Unauthorized");
  }
  isRateLimitError(error) {
    return error.message.includes("429") || error.message.includes("Too Many Requests");
  }
  // Retry mechanism for failed requests
  requestWithRetry(_0) {
    return __async(this, arguments, function* (endpoint, options = {}, maxRetries = 3) {
      let lastError;
      for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
          return yield this.request(endpoint, options);
        } catch (error) {
          lastError = error;
          if (this.isAuthError(error) || error.message.includes("4") && !this.isRateLimitError(error)) {
            throw error;
          }
          if (attempt < maxRetries) {
            const delay = Math.pow(2, attempt) * 1e3;
            yield new Promise((resolve) => setTimeout(resolve, delay));
          }
        }
      }
      throw lastError;
    });
  }
  // Batch operations
  batchRequest(requests) {
    return __async(this, null, function* () {
      return yield this.request("/batch", {
        method: "POST",
        body: JSON.stringify({ requests })
      });
    });
  }
  // Pagination helper
  getPaginatedData(_0) {
    return __async(this, arguments, function* (endpoint, options = {}) {
      const {
        limit = 50,
        maxItems = 200,
        onPage = () => {
        }
      } = options;
      const results = [];
      let offset = 0;
      let hasMore = true;
      while (hasMore && results.length < maxItems) {
        const params = new URLSearchParams({
          limit: limit.toString(),
          offset: offset.toString()
        });
        const response = yield this.request(`${endpoint}?${params}`);
        const items = response.data || response.items || response;
        results.push(...items);
        onPage(items, offset);
        hasMore = items.length === limit;
        offset += limit;
      }
      return results.slice(0, maxItems);
    });
  }
  // Health check
  healthCheck() {
    return __async(this, null, function* () {
      try {
        yield this.request("/health");
        return true;
      } catch (error) {
        console.warn("Health check failed:", error.message);
        return false;
      }
    });
  }
  // Team widget settings
  getTeamWidgetSettings() {
    return __async(this, null, function* () {
      return yield this.request(`${API_ENDPOINTS.teamWidgetSettings}${this.config.teamSlug}/`);
    });
  }
  // Convenience methods for HTTP requests
  get(_0) {
    return __async(this, arguments, function* (endpoint, options = {}) {
      return yield this.request(endpoint, __spreadValues({
        method: "GET"
      }, options));
    });
  }
  post(_0) {
    return __async(this, arguments, function* (endpoint, data = null, options = {}) {
      const requestOptions = __spreadValues({
        method: "POST"
      }, options);
      if (data) {
        requestOptions.body = JSON.stringify(data);
      }
      return yield this.request(endpoint, requestOptions);
    });
  }
}
class StorageService {
  constructor() {
    this.isAvailable = this.checkStorageAvailability();
    this.deviceId = this.getOrCreateDeviceId();
  }
  checkStorageAvailability() {
    try {
      const test = "__storage_test__";
      localStorage.setItem(test, test);
      localStorage.removeItem(test);
      return true;
    } catch (error) {
      console.warn("localStorage not available, using memory storage:", error.message);
      this.memoryStorage = /* @__PURE__ */ new Map();
      return false;
    }
  }
  getOrCreateDeviceId() {
    let deviceId = this.getItem(STORAGE_KEYS.DEVICE_ID);
    if (!deviceId) {
      deviceId = this.generateUUID();
      this.setItem(STORAGE_KEYS.DEVICE_ID, deviceId);
    }
    return deviceId;
  }
  /**
   * Generate a UUID v4 using crypto.randomUUID() with fallback for older browsers
   */
  generateUUID() {
    if (typeof crypto !== "undefined" && crypto.randomUUID) {
      return crypto.randomUUID();
    }
    if (typeof crypto !== "undefined" && crypto.getRandomValues) {
      return "10000000-1000-4000-8000-100000000000".replace(
        /[018]/g,
        (c) => (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
      );
    }
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
      const r = Math.random() * 16 | 0;
      const v = c === "x" ? r : r & 3 | 8;
      return v.toString(16);
    });
  }
  getDeviceId() {
    return this.deviceId;
  }
  setItem(key, value) {
    try {
      const serializedValue = JSON.stringify(value);
      if (this.isAvailable) {
        localStorage.setItem(key, serializedValue);
      } else {
        this.memoryStorage.set(key, serializedValue);
      }
    } catch (error) {
      console.warn("Failed to store item:", key, error);
    }
  }
  getItem(key) {
    try {
      let serializedValue;
      if (this.isAvailable) {
        serializedValue = localStorage.getItem(key);
      } else {
        serializedValue = this.memoryStorage.get(key);
      }
      return serializedValue ? JSON.parse(serializedValue) : null;
    } catch (error) {
      console.warn("Failed to retrieve item:", key, error);
      return null;
    }
  }
  removeItem(key) {
    try {
      if (this.isAvailable) {
        localStorage.removeItem(key);
      } else {
        this.memoryStorage.delete(key);
      }
    } catch (error) {
      console.warn("Failed to remove item:", key, error);
    }
  }
  clear() {
    try {
      if (this.isAvailable) {
        Object.values(STORAGE_KEYS).forEach((key) => {
          localStorage.removeItem(key);
        });
      } else {
        this.memoryStorage.clear();
      }
    } catch (error) {
      console.warn("Failed to clear storage:", error);
    }
  }
  // Conversation management
  saveConversation(conversation) {
    const conversations = this.getConversations();
    const existingIndex = conversations.findIndex((c) => c.sessionId === conversation.sessionId);
    if (existingIndex >= 0) {
      conversations[existingIndex] = conversation;
    } else {
      conversations.push(conversation);
    }
    const maxConversations = 50;
    if (conversations.length > maxConversations) {
      conversations.sort((a, b) => new Date(b.modified || b.created) - new Date(a.modified || a.created));
      conversations.splice(maxConversations);
    }
    this.setItem(STORAGE_KEYS.CONVERSATIONS, conversations);
  }
  getConversations() {
    return this.getItem(STORAGE_KEYS.CONVERSATIONS) || [];
  }
  clearConversations() {
    this.removeItem(STORAGE_KEYS.CONVERSATIONS);
  }
  deleteConversation(conversationId) {
    const conversations = this.getConversations();
    const filtered = conversations.filter((c) => c.sessionId !== conversationId);
    this.setItem(STORAGE_KEYS.CONVERSATIONS, filtered);
  }
  // Unread conversations management
  saveUnreadConversations(unreadSessionIds) {
    this.setItem(STORAGE_KEYS.UNREAD_CONVERSATIONS, unreadSessionIds);
  }
  getUnreadConversations() {
    return this.getItem(STORAGE_KEYS.UNREAD_CONVERSATIONS) || [];
  }
  clearUnreadConversations() {
    this.removeItem(STORAGE_KEYS.UNREAD_CONVERSATIONS);
  }
  // User management
  setUser(userData) {
    this.setItem(STORAGE_KEYS.USER_DATA, __spreadProps(__spreadValues({}, userData), {
      lastSeen: (/* @__PURE__ */ new Date()).toISOString()
    }));
  }
  getUser() {
    return this.getItem(STORAGE_KEYS.USER_DATA);
  }
  clearUser() {
    this.removeItem(STORAGE_KEYS.USER_DATA);
  }
  // Settings management
  saveSettings(settings) {
    const currentSettings = this.getSettings();
    this.setItem(STORAGE_KEYS.SETTINGS, __spreadValues(__spreadValues({}, currentSettings), settings));
  }
  setSettings(settings) {
    return this.saveSettings(settings);
  }
  getSettings() {
    return this.getItem(STORAGE_KEYS.SETTINGS) || {};
  }
  getSetting(key, defaultValue = null) {
    const settings = this.getSettings();
    return settings[key] !== void 0 ? settings[key] : defaultValue;
  }
  setSetting(key, value) {
    const settings = this.getSettings();
    settings[key] = value;
    this.setItem(STORAGE_KEYS.SETTINGS, settings);
  }
  // Data cleanup
  cleanupOldData(maxAge = 30) {
    const conversations = this.getConversations();
    const cutoffDate = /* @__PURE__ */ new Date();
    cutoffDate.setDate(cutoffDate.getDate() - maxAge);
    const filteredConversations = conversations.filter((conversation) => {
      const conversationDate = new Date(conversation.modified || conversation.created);
      return conversationDate > cutoffDate;
    });
    if (filteredConversations.length !== conversations.length) {
      this.setItem(STORAGE_KEYS.CONVERSATIONS, filteredConversations);
      console.log(`Cleaned up ${conversations.length - filteredConversations.length} old conversations`);
    }
  }
  // Export/Import functionality
  exportData() {
    return {
      conversations: this.getConversations(),
      user: this.getUser(),
      settings: this.getSettings(),
      deviceId: this.deviceId,
      exportedAt: (/* @__PURE__ */ new Date()).toISOString()
    };
  }
  importData(data) {
    try {
      if (data.conversations) {
        this.setItem(STORAGE_KEYS.CONVERSATIONS, data.conversations);
      }
      if (data.user) {
        this.setItem(STORAGE_KEYS.USER_DATA, data.user);
      }
      if (data.settings) {
        this.setItem(STORAGE_KEYS.SETTINGS, data.settings);
      }
      return true;
    } catch (error) {
      console.error("Failed to import data:", error);
      return false;
    }
  }
  // Storage size management
  getStorageSize() {
    if (!this.isAvailable) {
      return 0;
    }
    let total = 0;
    Object.values(STORAGE_KEYS).forEach((key) => {
      const item = localStorage.getItem(key);
      if (item) {
        total += item.length;
      }
    });
    return total;
  }
  getStorageSizeHuman() {
    const bytes = this.getStorageSize();
    if (bytes === 0) {
      return "0 B";
    }
    const k = 1024;
    const sizes = ["B", "KB", "MB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  }
  // Migration support
  migrateData(fromVersion, toVersion) {
    console.log(`Migrating storage from version ${fromVersion} to ${toVersion}`);
    if (fromVersion === "1.0.0" && toVersion === "1.1.0") {
      const conversations = this.getConversations();
      const migratedConversations = conversations.map((conv) => __spreadProps(__spreadValues({}, conv), {
        version: "1.1.0"
        // Add new fields or transform existing ones
      }));
      this.setItem(STORAGE_KEYS.CONVERSATIONS, migratedConversations);
    }
  }
  // Privacy compliance
  clearPersonalData() {
    this.clearUser();
    const conversations = this.getConversations();
    const anonymizedConversations = conversations.map((conv) => __spreadProps(__spreadValues({}, conv), {
      messages: conv.messages.map((msg) => __spreadProps(__spreadValues({}, msg), {
        // Remove personally identifiable information
        text: msg.type === "user" ? "[User message removed]" : msg.text
      }))
    }));
    this.setItem(STORAGE_KEYS.CONVERSATIONS, anonymizedConversations);
  }
  // Utility methods
  hasData() {
    return this.getConversations().length > 0 || this.getUser() !== null;
  }
  getDataSummary() {
    return {
      conversationCount: this.getConversations().length,
      hasUser: this.getUser() !== null,
      storageSize: this.getStorageSizeHuman(),
      deviceId: this.deviceId
    };
  }
}
function validateConfig(config) {
  const errors = [];
  if (!config) {
    errors.push("Configuration is required");
    return {
      isValid: false,
      errors
    };
  }
  if (!config.appId) {
    errors.push("appId is required");
  } else if (typeof config.appId !== "string") {
    errors.push("appId must be a string");
  }
  if (!config.teamSlug) {
    errors.push("teamSlug is required");
  } else if (typeof config.teamSlug !== "string") {
    errors.push("teamSlug must be a string");
  }
  if (config.apiBase !== void 0 && typeof config.apiBase !== "string") {
    errors.push("apiBase must be a string");
  }
  if (config.wsUrl !== void 0 && typeof config.wsUrl !== "string") {
    errors.push("wsUrl must be a string");
  }
  if (config.userJWT !== void 0 && typeof config.userJWT !== "string") {
    errors.push("userJWT must be a string");
  }
  if (config.autoStart !== void 0 && typeof config.autoStart !== "boolean") {
    errors.push("autoStart must be a boolean");
  }
  if (config.position !== void 0) {
    const validPositions = ["bottom-right", "bottom-left", "top-right", "top-left"];
    if (typeof config.position !== "string" || !validPositions.includes(config.position)) {
      errors.push(`position must be one of: ${validPositions.join(", ")}`);
    }
  }
  if (config.theme !== void 0 && typeof config.theme !== "object") {
    const validThemes = ["default", "dark", "light"];
    if (typeof config.theme === "string" && !validThemes.includes(config.theme)) {
      errors.push(`theme must be one of: ${validThemes.join(", ")}`);
    }
  }
  if (config.maxFileSize !== void 0) {
    if (typeof config.maxFileSize !== "number") {
      errors.push("maxFileSize must be a number");
    } else if (config.maxFileSize > 10 * 1024 * 1024) {
      errors.push("maxFileSize cannot exceed 10MB");
    }
  }
  if (config.allowedFileTypes !== void 0 && !Array.isArray(config.allowedFileTypes)) {
    errors.push("allowedFileTypes must be an array");
  }
  return {
    isValid: errors.length === 0,
    errors
  };
}
class TicketpingChat {
  constructor(config = {}) {
    this.config = __spreadValues(__spreadValues({}, DEFAULT_CONFIG), config);
    this.isInitialized = false;
    this.isOpen = false;
    this.currentUser = null;
    this.api = new ApiService(this.config);
    this.storage = new StorageService();
    this.ws = null;
    this.notifiWs = null;
    this.chatBubble = null;
    this.chatWindow = null;
    this.widgetContainer = null;
    this.conversations = /* @__PURE__ */ new Map();
    this.isChatSessionActive = false;
    this.currentChatSession = null;
    this.unreadCount = 0;
    this.unreadConversations = /* @__PURE__ */ new Set();
    this.teamSettings = null;
    this.init();
  }
  init() {
    return __async(this, null, function* () {
      if (this.isInitialized) {
        console.warn("TicketpingChat already initialized");
        return;
      }
      try {
        const validation = validateConfig(this.config);
        if (!validation.isValid) {
          throw new Error(`Invalid configuration: ${validation.errors.join(", ")}`);
        }
        yield this.fetchTeamSettings();
        this.createWidgetContainer();
        this.applyWidgetPosition();
        this.chatBubble = new ChatBubble(this.widgetContainer, {
          onClick: () => this.toggle(),
          iconColor: this.getIconColor()
        });
        if (!this.shouldShowBubble()) {
          this.chatBubble.hide();
          this.track("widget_bubble_hidden_by_settings");
        }
        this.chatWindow = new ChatWindow(this.widgetContainer, {
          onClose: () => this.close(),
          onTabSwitch: (tab) => this.handleTabSwitch(tab),
          onSendMessage: (message) => this.sendMessage(message),
          onFileUpload: (file) => this.handleFileUpload(file),
          onConversationSelect: (sessionId) => this.loadConversation(sessionId),
          onBackButtonClick: () => this.backToList(),
          teamLogoIcon: this.getTeamLogoIcon(),
          teamSettings: this.teamSettings
        });
        yield this.loadStoredConversations();
        this.initNotificationWebSocket();
        this.isInitialized = true;
        this.track("widget_initialized", {
          appId: this.config.appId,
          version: "1.0.0"
        });
      } catch (error) {
        console.error("Failed to initialize TicketpingChat:", error);
        this.track("widget_init_error", { error: error.message });
      }
    });
  }
  /**
   * Fetch team widget settings from server
   */
  fetchTeamSettings() {
    return __async(this, null, function* () {
      var _a, _b;
      try {
        this.teamSettings = yield this.api.getTeamWidgetSettings();
        this.track("team_settings_loaded", {
          teamSlug: (_a = this.teamSettings) == null ? void 0 : _a.teamSlug,
          isAvailable: (_b = this.teamSettings) == null ? void 0 : _b.isAvailable
        });
      } catch (error) {
        console.warn("Failed to fetch team settings, using defaults:", error);
        this.teamSettings = null;
      }
    });
  }
  /**
   * Get team logo icon from team settings or config
   */
  getTeamLogoIcon() {
    var _a;
    return this.config.teamLogoIcon || ((_a = this.teamSettings) == null ? void 0 : _a.logoUrl) || null;
  }
  /**
   * Check if widget bubble should be shown based on team settings
   */
  shouldShowBubble() {
    if (!this.teamSettings || this.teamSettings.widgetBubbleVisible === void 0) {
      return true;
    }
    return this.teamSettings.widgetBubbleVisible;
  }
  /**
   * Apply widget position from team settings
   */
  applyWidgetPosition() {
    var _a;
    if (!this.widgetContainer) {
      return;
    }
    const position = ((_a = this.teamSettings) == null ? void 0 : _a.widgetPosition) || this.config.position || "bottom-right";
    const normalizedPosition = position === "left" ? "bottom-left" : "bottom-right";
    this.widgetContainer.classList.remove("position-bottom-left", "position-bottom-right");
    if (normalizedPosition === "bottom-left") {
      this.widgetContainer.classList.add("position-bottom-left");
    }
  }
  backToList() {
    this.isChatSessionActive = false;
    this.currentChatSession = null;
    if (this.ws) {
      this.ws.disconnect();
    }
    this.chatWindow.setConversations(
      Array.from(this.conversations.values()),
      this.unreadConversations
    );
    this.track("back_to_list");
  }
  startConversation() {
    return __async(this, null, function* () {
      const result = yield this.api.createChatSession();
      this.currentChatSession = result.sessionId;
      yield this.initWsConversation(result.sessionId);
      this.isChatSessionActive = true;
    });
  }
  initWsConversation(sessionId) {
    return __async(this, null, function* () {
      try {
        if (this.config.userJWT) {
          yield this.initAuthWsConversation(sessionId);
        } else {
          yield this.initAnonWsConversation(sessionId);
        }
      } catch (error) {
        console.warn("WebSocket initialization failed:", error);
        this.track("websocket_init_error", { error: error.message });
        throw error;
      }
    });
  }
  initAnonWsConversation(sessionId) {
    return __async(this, null, function* () {
      return new Promise((resolve, reject) => {
        try {
          let wsUrl = `${this.config.wsBase}/ws/chat/${this.config.teamSlug}/`;
          if (sessionId) {
            wsUrl = `${this.config.wsBase}/ws/chat/${this.config.teamSlug}/${sessionId}/`;
          }
          this.ws = new WebSocketService(wsUrl, null, {
            onSessionState: (data) => {
              this.handleSessionState(data);
              resolve();
            },
            onMessage: (message) => this.handleWebSocketMessage(message),
            onFileAttachment: (data) => this.handleWebSocketMessage(data),
            onMessageHistory: (data) => this.handleMessageHistory(data),
            onTyping: (data) => this.handleTypingIndicator(data),
            onStatusChange: (status) => this.handleAgentStatus(status),
            onError: (error) => {
              this.handleWebSocketError(error);
              reject(error);
            }
          });
          setTimeout(() => {
            if (this.ws && !this.ws.isWsConnected()) {
              resolve();
            }
          }, 5e3);
        } catch (error) {
          console.warn("WebSocket initialization failed:", error);
          this.track("websocket_init_error", { error: error.message });
          reject(error);
        }
      });
    });
  }
  initAuthWsConversation(sessionId) {
    return __async(this, null, function* () {
      return new Promise((resolve, reject) => __async(this, null, function* () {
        try {
          const { chatJWT } = yield this.api.getChatToken();
          let wsUrl = `${this.config.wsBase}/ws/chat/${this.config.teamSlug}/?jwt=${chatJWT}`;
          if (sessionId) {
            wsUrl = `${this.config.wsBase}/ws/chat/${this.config.teamSlug}/${sessionId}/?jwt=${chatJWT}`;
          }
          this.ws = new WebSocketService(wsUrl, chatJWT, {
            onSessionState: (data) => {
              this.handleSessionState(data);
              resolve();
            },
            onMessage: (message) => this.handleWebSocketMessage(message),
            onFileAttachment: (data) => this.handleWebSocketMessage(data),
            onMessageHistory: (data) => this.handleMessageHistory(data),
            onTyping: (data) => this.handleTypingIndicator(data),
            onStatusChange: (status) => this.handleAgentStatus(status),
            onError: (error) => {
              this.handleWebSocketError(error);
              reject(error);
            }
          });
          setTimeout(() => {
            if (this.ws && !this.ws.isWsConnected()) {
              resolve();
            }
          }, 5e3);
        } catch (error) {
          console.warn("WebSocket auth initialization failed:", error);
          this.track("websocket_auth_init_error", { error: error.message });
          reject(error);
        }
      }));
    });
  }
  /**
   * Initialize persistent notification WebSocket for real-time unread count updates
   */
  initNotificationWebSocket() {
    if (this.notifiWs) {
      this.notifiWs.disconnect();
    }
    const wsUrl = `${this.config.wsBase}/ws/customer-notifs/${this.config.teamSlug}/`;
    this.notifiWs = new NotificationWS(wsUrl, {
      onUnreadCount: (count) => this.handleUnreadCountUpdate(count),
      onConnect: () => {
        console.log("Notification WebSocket connected");
        this.track("notification_ws_connected");
      },
      onDisconnect: () => {
        console.log("Notification WebSocket disconnected");
      },
      onError: (error) => {
        console.warn("Notification WebSocket error:", error);
      }
    });
  }
  /**
   * Handle unread count updates from notification WebSocket
   */
  handleUnreadCountUpdate(count) {
    this.unreadCount = count;
    if (count > 0 && !this.isOpen) {
      this.chatBubble.showNotificationBadge(count);
    } else {
      this.chatBubble.hideNotificationBadge();
    }
    this.chatWindow.setMessagesTabUnread(count > 0);
    this.refreshConversations();
    this.track("unread_count_updated", { count });
  }
  /**
   * Refresh conversations from server to get updated unread state
   */
  refreshConversations() {
    return __async(this, null, function* () {
      try {
        if (this.config.userJWT) {
          const serverConversations = yield this.api.getConversations();
          if (serverConversations && serverConversations.results) {
            this.unreadConversations.clear();
            serverConversations.results.forEach((conv) => {
              const existing = this.conversations.get(conv.sessionId);
              const messages = (existing == null ? void 0 : existing.messages) || conv.messages || [];
              this.conversations.set(conv.sessionId, __spreadProps(__spreadValues({}, conv), { messages }));
              if (conv.hasUnread) {
                this.unreadConversations.add(conv.sessionId);
              }
            });
            this.chatWindow.setConversations(
              Array.from(this.conversations.values()),
              this.unreadConversations
            );
          }
        }
      } catch (error) {
        console.warn("Failed to refresh conversations:", error);
      }
    });
  }
  createWidgetContainer() {
    const existing = document.querySelector(".ticketping-widget");
    if (existing) {
      existing.remove();
    }
    this.widgetContainer = createDOMElement("div", {
      className: "ticketping-widget",
      "data-version": "1.0.0"
    });
    this.applyCustomTheme();
    document.body.appendChild(this.widgetContainer);
  }
  applyCustomTheme() {
    if (!this.config.theme) {
      return;
    }
    const theme = this.config.theme;
    const root = document.documentElement;
    const themeMap = {
      primaryColor: "--tp-primary-color",
      primaryButtonText: "--tp-primary-button-text",
      primaryHover: "--tp-primary-hover",
      textPrimary: "--tp-text-primary",
      textSecondary: "--tp-text-secondary",
      textMuted: "--tp-text-muted",
      background: "--tp-background",
      backgroundSecondary: "--tp-background-secondary",
      backgroundTertiary: "--tp-background-tertiary",
      border: "--tp-border",
      borderLight: "--tp-border-light",
      notificationBg: "--tp-notification-bg",
      successColor: "--tp-success-color",
      offlineColor: "--tp-offline-color",
      errorBg: "--tp-error-bg",
      errorText: "--tp-error-text",
      errorBorder: "--tp-error-border",
      shadowLight: "--tp-shadow-light",
      shadowMedium: "--tp-shadow-medium",
      shadowDark: "--tp-shadow-dark",
      iconColor: "--tp-icon-color"
    };
    Object.entries(themeMap).forEach(([themeKey, cssVar]) => {
      if (theme[themeKey]) {
        root.style.setProperty(cssVar, theme[themeKey]);
      }
    });
  }
  getIconColor() {
    if (this.config.theme && typeof this.config.theme === "object") {
      return this.config.theme.iconColor;
    }
    return null;
  }
  // Public API methods
  open() {
    if (!this.isInitialized) {
      return;
    }
    this.isOpen = true;
    this.chatBubble.setOpen(true);
    this.chatWindow.show();
    this.clearUnreadNotification();
    this.track("widget_opened");
  }
  clearUnreadNotification() {
    this.unreadCount = 0;
    this.chatBubble.hideNotificationBadge();
  }
  markConversationAsRead(sessionId) {
    if (this.unreadConversations.has(sessionId)) {
      this.unreadConversations.delete(sessionId);
      this.chatWindow.setConversations(
        Array.from(this.conversations.values()),
        this.unreadConversations
      );
      if (this.ws && this.ws.isWsConnected()) {
        this.ws.markRead(sessionId);
      }
    }
  }
  close() {
    if (!this.isInitialized) {
      return;
    }
    if (this.ws) {
      this.ws.disconnect();
    }
    this.isOpen = false;
    this.chatBubble.setOpen(false);
    this.chatWindow.hide();
    this.track("widget_closed");
  }
  toggle() {
    if (this.isOpen) {
      this.close();
    } else {
      this.open();
    }
  }
  identify(userData) {
    return __async(this, null, function* () {
      this.currentUser = userData;
      this.storage.setUser(userData);
      if (userData.userJWT && userData.userJWT !== "your-actual-jwt-token-here") {
        this.config.userJWT = userData.userJWT;
        try {
          yield this.loadAuthenticatedUserData();
          yield this.reinitializeWebSocketIfNeeded();
          this.track("user_identified", {
            userId: userData.id || userData.email || "unknown",
            hasJWT: !!userData.userJWT
          });
        } catch (error) {
          console.warn("Failed to initialize authenticated user features:", error);
          this.track("user_identify_error", { error: error.message });
        }
      }
    });
  }
  sendMessage(messageData) {
    return __async(this, null, function* () {
      console.log("sendMessage", messageData);
      if (!this.currentChatSession) {
        throw new Error("No conversation started!");
      }
      const message = __spreadValues({
        sessionId: this.currentChatSession,
        type: "user_message",
        sender: "USER",
        messageText: messageData.text,
        created: (/* @__PURE__ */ new Date()).toISOString()
      }, messageData);
      console.log("message", message);
      this.addMessageToConversation(this.currentChatSession, message);
      this.chatWindow.addMessage(message);
      if (this.ws && this.ws.isWsConnected()) {
        this.ws.sendMessage(message);
      } else {
        yield this.api.sendMessage(message);
      }
      this.track("message_sent", {
        chatSessionId: this.currentChatSession,
        messageType: message.type,
        hasAttachment: !!message.file
      });
    });
  }
  handleFileUpload(file) {
    return __async(this, null, function* () {
      try {
        if (file.size > this.config.maxFileSize) {
          throw new Error(`File size exceeds ${this.config.maxFileSize / 1024 / 1024}MB limit`);
        }
        yield this.api.uploadFile(file, this.currentChatSession);
      } catch (error) {
        console.error("File upload failed:", error);
        this.chatWindow.showError("Failed to upload file: " + error.message);
        this.track("file_upload_error", { error: error.message });
      }
    });
  }
  handleSessionState(data) {
    console.log("handleSessionState", data);
    this.currentChatSession = data.sessionId;
    this.conversations.set(data.sessionId, {
      sessionId: data.sessionId,
      messages: [],
      created: data.created
    });
    this.chatWindow.showConversationItem(this.currentChatSession);
  }
  handleWebSocketMessage(data) {
    this.addMessageToConversation(data.sessionId, data);
    if (data.sessionId === this.currentChatSession) {
      this.chatWindow.addMessage(data);
    }
    if (data.sender !== "USER") {
      const isViewingThisConversation = this.isOpen && data.sessionId === this.currentChatSession;
      if (!isViewingThisConversation) {
        this.unreadConversations.add(data.sessionId);
        this.chatWindow.setConversations(
          Array.from(this.conversations.values()),
          this.unreadConversations
        );
      }
    }
  }
  handleMessageHistory(data) {
    console.log("handleMessageHistory", data);
    console.log("this.conversations", this.conversations);
    this.conversations.set(data.sessionId, {
      sessionId: data.sessionId,
      messages: data.messages,
      created: data.created
    });
    this.chatWindow.setMessages(data.messages);
  }
  handleTabSwitch(tab) {
    this.track("tab_switched", { tab });
  }
  loadConversation(chatSessionId) {
    return __async(this, null, function* () {
      try {
        if (!chatSessionId || chatSessionId === "new") {
          this.chatWindow.switchTab("messages");
          this.chatWindow.showLoadingState();
          yield this.startConversation();
          this.chatWindow.showConversationItem();
          return;
        }
        this.chatWindow.switchTab("messages");
        this.chatWindow.showLoadingState();
        yield this.initWsConversation(chatSessionId);
        this.isChatSessionActive = true;
        this.markConversationAsRead(chatSessionId);
        this.chatWindow.showConversationItem();
        this.track("conversation_loaded", { chatSessionId });
      } catch (error) {
        console.error("Failed to load conversation:", error);
        this.chatWindow.hideLoadingState();
        this.chatWindow.showError("Failed to load conversation");
      }
    });
  }
  loadStoredConversations() {
    return __async(this, null, function* () {
      try {
        const stored = this.storage.getConversations();
        stored.forEach((conv) => this.conversations.set(conv.sessionId, conv));
        if (this.config.userJWT) {
          const serverConversations = yield this.api.getConversations();
          serverConversations["results"].forEach((conv) => {
            this.conversations.set(conv.sessionId, conv);
            this.storage.saveConversation(conv);
            if (conv.hasUnread) {
              this.unreadConversations.add(conv.sessionId);
            }
          });
        }
        this.chatWindow.setConversations(
          Array.from(this.conversations.values()),
          this.unreadConversations
        );
      } catch (error) {
        console.warn("Failed to load conversations:", error);
      }
    });
  }
  /**
   * Load authenticated user data including conversations and user preferences
   */
  loadAuthenticatedUserData() {
    return __async(this, null, function* () {
      if (!this.config.userJWT) {
        return;
      }
      try {
        const serverConversations = yield this.api.getConversations();
        if (serverConversations && serverConversations.results) {
          this.conversations.clear();
          this.unreadConversations.clear();
          serverConversations.results.forEach((conv) => {
            this.conversations.set(conv.sessionId, conv);
            this.storage.saveConversation(conv);
            if (conv.hasUnread) {
              this.unreadConversations.add(conv.sessionId);
            }
          });
          if (this.chatWindow) {
            this.chatWindow.setConversations(
              Array.from(this.conversations.values()),
              this.unreadConversations
            );
          }
          console.log(`Loaded ${serverConversations.results.length} conversations for authenticated user`);
        }
      } catch (error) {
        console.warn("Failed to load authenticated user data:", error);
        yield this.loadStoredConversations();
        throw error;
      }
    });
  }
  /**
   * Reinitialize WebSocket connection with authentication if needed
   */
  reinitializeWebSocketIfNeeded() {
    return __async(this, null, function* () {
      if (!this.isChatSessionActive || !this.currentChatSession) {
        return;
      }
      try {
        console.log("Reinitializing WebSocket with authentication...");
        if (this.ws) {
          this.ws.disconnect();
          this.ws = null;
        }
        yield this.initWsConversation(this.currentChatSession);
        console.log("WebSocket reinitialized with authentication successfully");
        this.track("websocket_reinitialized", {
          sessionId: this.currentChatSession,
          authenticated: true
        });
      } catch (error) {
        console.error("Failed to reinitialize WebSocket:", error);
        this.track("websocket_reinit_error", {
          error: error.message,
          sessionId: this.currentChatSession
        });
      }
    });
  }
  // Utility methods
  addMessageToConversation(chatSessionId, message) {
    const conversation = this.conversations.get(chatSessionId);
    console.log("addMessageToConversation", conversation, chatSessionId, message);
    if (conversation) {
      conversation.messages.push(message);
      conversation.modified = /* @__PURE__ */ new Date();
      this.storage.saveConversation(conversation);
    }
  }
  track(event, data = {}) {
    if (this.config.analytics && true) {
      this.api.track(event, __spreadProps(__spreadValues({}, data), {
        timestamp: (/* @__PURE__ */ new Date()).toISOString(),
        appId: this.config.appId,
        version: "1.0.0"
      }));
    }
  }
  handleWebSocketError(error) {
    console.warn("WebSocket error:", error);
    this.track("websocket_error", { error: error.message });
  }
  handleAgentStatus(status) {
    this.chatWindow.updateAgentStatus(status);
  }
  handleAgentJoined(data) {
    this.chatWindow.showAgentJoined(data.agent);
  }
  handleTypingIndicator(data) {
    if (data.sessionId === this.currentChatSession) {
      this.chatWindow.showTypingIndicator(data.typing);
    }
  }
  // Cleanup
  destroy() {
    if (this.ws) {
      this.ws.disconnect();
    }
    if (this.notifiWs) {
      this.notifiWs.disconnect();
    }
    if (this.widgetContainer) {
      this.widgetContainer.remove();
    }
    this.isInitialized = false;
    this.isChatSessionActive = false;
    this.currentChatSession = null;
    this.currentUser = null;
    this.track("widget_destroyed");
  }
}
window.TicketpingChat = {
  instance: null,
  init(config = {}) {
    if (this.instance) {
      console.warn("TicketpingChat already initialized");
      return this.instance;
    }
    this.instance = new TicketpingChat(config);
    return this.instance;
  },
  identify(userData) {
    if (this.instance) {
      return this.instance.identify(userData);
    }
  },
  open() {
    if (this.instance) {
      this.instance.open();
    }
  },
  close() {
    if (this.instance) {
      this.instance.close();
    }
  },
  startConversation() {
    if (this.instance) {
      return this.instance.startConversation();
    }
  },
  destroy() {
    if (this.instance) {
      this.instance.destroy();
      this.instance = null;
    }
  },
  // Version info
  version: "1.0.0"
};
if (window.ticketpingConfig) {
  window.TicketpingChat.init(window.ticketpingConfig);
}
export {
  TicketpingChat as default
};
//# sourceMappingURL=widget.esm.js.map