(function () {
  if (window.GIAComposePanelThread) {
    return;
  }

  const core = window.GIAComposePanelCore;
  if (!core) {
    return;
  }

  const createController = function (config) {
    const panel = config.panel;
    const state = config.state;
    const thread = config.thread;
    const textarea = config.textarea;
    const typingNode = config.typingNode;
    const hiddenReplyTo = config.hiddenReplyTo;
    const replyBanner = config.replyBanner;
    const replyBannerText = config.replyBannerText;
    const replyClearBtn = config.replyClearBtn;
    const platformSelect = config.platformSelect;
    const contactSelect = config.contactSelect;
    const hiddenService = config.hiddenService;
    const hiddenIdentifier = config.hiddenIdentifier;
    const hiddenPerson = config.hiddenPerson;
    const metaLine = config.metaLine;
    const renderMode = config.renderMode;

    let lastTs = core.toInt(thread.dataset.lastTs);
    let beforeContextReset = null;

    const nearBottom = function () {
      return thread.scrollHeight - thread.scrollTop - thread.clientHeight < 120;
    };

    const scrollToBottom = function (force) {
      if (force || nearBottom()) {
        thread.scrollTop = thread.scrollHeight;
      }
    };

    const queryParams = function (extraParams) {
      const params = new URLSearchParams();
      params.set("service", thread.dataset.service || "");
      params.set("identifier", thread.dataset.identifier || "");
      if (thread.dataset.person) {
        params.set("person", thread.dataset.person);
      }
      params.set("limit", thread.dataset.limit || "60");
      const extras =
        extraParams && typeof extraParams === "object" ? extraParams : {};
      Object.keys(extras).forEach(function (key) {
        const value = extras[key];
        if (value === undefined || value === null || value === "") {
          return;
        }
        params.set(String(key), String(value));
      });
      return params;
    };

    const ensureEmptyState = function (messageText) {
      if (thread.querySelector(".compose-row")) {
        const empty = thread.querySelector(".compose-empty");
        if (empty) {
          empty.remove();
        }
        return;
      }
      let empty = thread.querySelector(".compose-empty");
      if (!empty) {
        empty = core.createNode("p", "compose-empty");
        thread.appendChild(empty);
      }
      empty.textContent = String(
        messageText || "No stored messages for this contact yet."
      );
    };

    const rowByMessageId = function (messageId) {
      const targetId = String(messageId || "").trim();
      if (!targetId) {
        return null;
      }
      return thread.querySelector(
        '.compose-row[data-message-id="' + targetId + '"]'
      );
    };

    const clearReplySelectionClass = function () {
      thread
        .querySelectorAll(".compose-row.compose-reply-selected")
        .forEach(function (row) {
          row.classList.remove("compose-reply-selected");
        });
    };

    const flashReplyTarget = function (row) {
      if (!row) {
        return;
      }
      row.classList.remove("is-target-flash");
      void row.offsetWidth;
      row.classList.add("is-target-flash");
      window.setTimeout(function () {
        row.classList.remove("is-target-flash");
      }, 1000);
    };

    const clearReplyTarget = function () {
      if (hiddenReplyTo) {
        hiddenReplyTo.value = "";
      }
      if (replyBanner) {
        replyBanner.classList.add("is-hidden");
      }
      if (replyBannerText) {
        replyBannerText.textContent = "";
      }
      clearReplySelectionClass();
    };

    const setReplyTarget = function (messageId, preview) {
      const targetId = String(messageId || "").trim();
      if (!targetId) {
        clearReplyTarget();
        return;
      }
      const row = rowByMessageId(targetId);
      const snippet = core.normalizeSnippet(
        (row && row.dataset ? row.dataset.replySnippet : "") || preview || ""
      );
      if (hiddenReplyTo) {
        hiddenReplyTo.value = targetId;
      }
      if (replyBannerText) {
        replyBannerText.textContent = snippet;
      }
      if (replyBanner) {
        replyBanner.classList.remove("is-hidden");
      }
      clearReplySelectionClass();
      if (row) {
        row.classList.add("compose-reply-selected");
      }
    };

    const parseMessageRows = function (html) {
      const markup = String(html || "").trim();
      if (!markup) {
        return [];
      }
      const template = document.createElement("template");
      template.innerHTML = markup;
      return Array.from(template.content.querySelectorAll(".compose-row"));
    };

    const insertRowByTs = function (row) {
      const newTs = core.toInt(row.dataset.ts);
      const rows = Array.from(thread.querySelectorAll(".compose-row"));
      if (!rows.length) {
        thread.appendChild(row);
        return;
      }
      for (let index = rows.length - 1; index >= 0; index -= 1) {
        const existing = rows[index];
        if (core.toInt(existing.dataset.ts) <= newTs) {
          if (existing.nextSibling) {
            thread.insertBefore(row, existing.nextSibling);
          } else {
            thread.appendChild(row);
          }
          return;
        }
      }
      thread.insertBefore(row, rows[0]);
    };

    const upsertMessageRow = function (row) {
      if (!row || !row.classList || !row.classList.contains("compose-row")) {
        return;
      }
      const messageId = String((row.dataset && row.dataset.messageId) || "").trim();
      if (messageId) {
        const existing = rowByMessageId(messageId);
        if (existing) {
          existing.remove();
        }
      }
      const empty = thread.querySelector(".compose-empty");
      if (empty) {
        empty.remove();
      }
      insertRowByTs(row);
      lastTs = Math.max(lastTs, core.toInt(row.dataset.ts));
      thread.dataset.lastTs = String(lastTs);
    };

    const appendMessageHtml = function (html, forceScroll) {
      const rows = parseMessageRows(html);
      const shouldStick = forceScroll || nearBottom();
      rows.forEach(function (msg) {
        upsertMessageRow(msg);
      });
      if (rows.length) {
        scrollToBottom(shouldStick);
      }
      ensureEmptyState();
    };

    const applyTyping = function (payload) {
      if (!typingNode) {
        return;
      }
      const typingPayload =
        payload && typeof payload === "object" ? payload : {};
      if (!typingPayload.typing) {
        typingNode.classList.add("is-hidden");
        return;
      }
      const displayName = String(typingPayload.display_name || "").trim();
      typingNode.textContent = (displayName || "Contact") + " is typing...";
      typingNode.classList.remove("is-hidden");
    };

    const closeSocket = function () {
      if (!state.socket) {
        return;
      }
      try {
        state.socket.close();
      } catch (_err) {
        // Ignore close failures.
      }
      state.socket = null;
      state.websocketReady = false;
    };

    const poll = async function (forceScroll) {
      if (state.polling || state.websocketReady) {
        return;
      }
      state.polling = true;
      try {
        const response = await fetch(
          thread.dataset.pollUrl + "?" + queryParams({ after_ts: String(lastTs) }),
          {
            method: "GET",
            credentials: "same-origin",
            headers: { Accept: "application/json" },
          }
        );
        if (!response.ok) {
          return;
        }
        const payload = await response.json();
        appendMessageHtml(payload.messages_html || "", forceScroll);
        applyTyping(payload.typing);
        if (payload.last_ts !== undefined && payload.last_ts !== null) {
          lastTs = Math.max(lastTs, core.toInt(payload.last_ts));
          thread.dataset.lastTs = String(lastTs);
        }
      } catch (err) {
        console.debug("compose poll error", err);
      } finally {
        state.polling = false;
      }
    };

    const setupWebSocket = function () {
      const wsPath = String(thread.dataset.wsUrl || "").trim();
      if (!wsPath || !window.WebSocket) {
        return;
      }
      try {
        const protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
        const socket = new WebSocket(protocol + window.location.host + wsPath);
        state.socket = socket;
        socket.onopen = function () {
          state.websocketReady = true;
          try {
            socket.send(JSON.stringify({ kind: "sync", last_ts: lastTs }));
          } catch (_err) {
            // Ignore sync send errors.
          }
        };
        socket.onmessage = function (event) {
          const payload = core.parseJsonSafe(event.data || "{}", {});
          appendMessageHtml(payload.messages_html || "", false);
          applyTyping(payload.typing);
          if (payload.last_ts !== undefined && payload.last_ts !== null) {
            lastTs = Math.max(lastTs, core.toInt(payload.last_ts));
            thread.dataset.lastTs = String(lastTs);
          }
        };
        socket.onclose = function () {
          state.websocketReady = false;
          if (state.socket === socket) {
            state.socket = null;
          }
        };
        socket.onerror = function () {
          state.websocketReady = false;
        };
      } catch (_err) {
        state.websocketReady = false;
        state.socket = null;
      }
    };

    const switchThreadContext = function (
      nextService,
      nextIdentifier,
      nextPersonId,
      nextUrl
    ) {
      const service = String(nextService || "").trim().toLowerCase();
      const identifier = core.normalizeIdentifierForService(
        service,
        nextIdentifier
      );
      const personId = String(nextPersonId || "").trim();
      if (!service || !identifier) {
        return;
      }
      if (renderMode === "page" && nextUrl) {
        window.location.assign(String(nextUrl));
        return;
      }
      if (
        String(thread.dataset.service || "").toLowerCase() === service
        && String(thread.dataset.identifier || "") === identifier
        && String(thread.dataset.person || "") === personId
      ) {
        return;
      }
      if (typeof beforeContextReset === "function") {
        beforeContextReset();
      }
      thread.dataset.service = service;
      thread.dataset.identifier = identifier;
      if (personId) {
        thread.dataset.person = personId;
      } else {
        delete thread.dataset.person;
      }
      if (hiddenService) {
        hiddenService.value = service;
      }
      if (hiddenIdentifier) {
        hiddenIdentifier.value = identifier;
      }
      if (hiddenPerson) {
        hiddenPerson.value = personId;
      }
      if (metaLine) {
        metaLine.textContent = core.titleCase(service) + " · " + identifier;
      }
      clearReplyTarget();
      closeSocket();
      lastTs = 0;
      thread.dataset.lastTs = "0";
      thread.innerHTML = "";
      ensureEmptyState("Loading messages...");
      applyTyping({ typing: false });
      poll(true);
      setupWebSocket();
    };

    const bindContextSelectors = function () {
      if (platformSelect) {
        platformSelect.addEventListener("change", function () {
          const selected = platformSelect.options[platformSelect.selectedIndex];
          if (!selected) {
            return;
          }
          const targetUrl = core.buildComposeUrl(
            renderMode,
            selected.value || "",
            selected.dataset.identifier || "",
            selected.dataset.person || ""
          );
          switchThreadContext(
            selected.value || "",
            selected.dataset.identifier || "",
            selected.dataset.person || "",
            targetUrl
          );
        });
      }

      if (contactSelect) {
        contactSelect.addEventListener("change", function () {
          const selected = contactSelect.options[contactSelect.selectedIndex];
          if (!selected) {
            return;
          }
          const serviceMap = core.parseServiceMap(selected);
          const currentService = String(thread.dataset.service || "").toLowerCase();
          const availableServices = Object.keys(serviceMap);
          let selectedService = currentService || String(selected.dataset.service || "");
          let selectedIdentifier = String(serviceMap[selectedService] || "").trim();
          if (!selectedIdentifier) {
            selectedService = String(
              selected.dataset.service || selectedService
            ).trim().toLowerCase();
            selectedIdentifier = String(serviceMap[selectedService] || "").trim();
          }
          if (!selectedIdentifier && availableServices.length) {
            selectedService = availableServices[0];
            selectedIdentifier = String(serviceMap[selectedService] || "").trim();
          }
          const targetUrl = core.buildComposeUrl(
            renderMode,
            selectedService,
            selectedIdentifier,
            selected.dataset.person || ""
          );
          switchThreadContext(
            selectedService,
            selectedIdentifier,
            selected.dataset.person || "",
            targetUrl
          );
        });
      }
    };

    const bindThreadEvents = function () {
      if (replyClearBtn) {
        replyClearBtn.addEventListener("click", function () {
          clearReplyTarget();
          textarea.focus();
        });
      }

      thread.addEventListener("click", function (event) {
        const replyLink =
          event.target.closest && event.target.closest(".compose-reply-link");
        if (replyLink) {
          const replyRef = replyLink.closest(".compose-reply-ref");
          const targetId = String(
            (replyRef && replyRef.dataset && replyRef.dataset.replyTargetId) || ""
          ).trim();
          if (!targetId) {
            return;
          }
          const targetRow = rowByMessageId(targetId);
          if (!targetRow) {
            return;
          }
          targetRow.scrollIntoView({ behavior: "smooth", block: "center" });
          flashReplyTarget(targetRow);
          return;
        }

        const replyButton =
          event.target.closest && event.target.closest(".compose-reply-btn");
        if (!replyButton) {
          return;
        }
        const row = replyButton.closest(".compose-row");
        if (!row) {
          return;
        }
        setReplyTarget(row.dataset.messageId || "", row.dataset.replySnippet || "");
        textarea.focus();
      });
    };

    const init = function () {
      bindThreadEvents();
      bindContextSelectors();
      applyTyping(core.parseJsonSafe(panel.dataset.initialTyping || "{}", {}));
      ensureEmptyState();
      scrollToBottom(true);
      setupWebSocket();

      state.pollTimer = setInterval(function () {
        if (!document.getElementById(config.panelId)) {
          if (window.GIAComposePanel) {
            window.GIAComposePanel.destroyPanel(config.panelId);
          }
          return;
        }
        poll(false);
      }, 4000);
    };

    return {
      clearReplyTarget: clearReplyTarget,
      init: init,
      poll: poll,
      queryParams: queryParams,
      setBeforeContextReset: function (callback) {
        beforeContextReset = callback;
      },
    };
  };

  window.GIAComposePanelThread = {
    createController: createController,
  };
})();
