const STORAGE = {
  session: "cobalt_session_v2",
  messages: "cobalt_messages_v2",
  activeContact: "cobalt_active_contact",
  theme: "cobalt_theme",
};

const API = {
  async request(pathname, { method = "GET", body, token } = {}) {
    const response = await fetch(pathname, {
      method,
      headers: {
        ...(body ? { "Content-Type": "application/json" } : {}),
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
      },
      ...(body ? { body: JSON.stringify(body) } : {}),
    });
    let payload = null;
    try {
      payload = await response.json();
    } catch {
      payload = null;
    }
    if (!response.ok) {
      throw new Error(payload?.error || "ارتباط با سرور برقرار نشد.");
    }
    return payload;
  },
  register: (data) => API.request("/api/register", { method: "POST", body: data }),
  login: (data) => API.request("/api/login", { method: "POST", body: data }),
  session: (token) => API.request("/api/session", { token }),
};

function getSession() {
  const session = safeParse(localStorage.getItem(STORAGE.session), null);
  return session?.token && session?.user ? session : null;
}

function setSession(session) {
  localStorage.setItem(STORAGE.session, JSON.stringify(session));
}

function clearSession() {
  localStorage.removeItem(STORAGE.session);
}


const svgIcons = {
  check: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m7.5 12.5 3 3 6-7"/></svg>',
  info: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M11 10h2v8h-2v-8Zm1-7a2 2 0 1 1 0 4 2 2 0 0 1 0-4Zm0-2a11 11 0 1 0 0 22 11 11 0 0 0 0-22Z"/></svg>',
  reply:
    '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m9 7-6 5 6 5v-3c5 0 8 1 11 4-1-5-4-9-11-9V7Z"/></svg>',
  smile:
    '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM8.5 8A1.5 1.5 0 1 1 7 9.5 1.5 1.5 0 0 1 8.5 8Zm7 0A1.5 1.5 0 1 1 14 9.5 1.5 1.5 0 0 1 15.5 8ZM12 18a6 6 0 0 1-5.2-3h10.4a6 6 0 0 1-5.2 3Z"/></svg>',
  file:
    '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6Zm1 3.5L18.5 9H15V5.5Z"/></svg>',
  download:
    '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M11 3h2v10l3.5-3.5 1.4 1.4-5.9 5.9-5.9-5.9 1.4-1.4L11 13V3ZM4 19h16v2H4v-2Z"/></svg>',
  search:
    '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m20 18.6-4.2-4.2a7 7 0 1 0-1.4 1.4l4.2 4.2 1.4-1.4ZM5 10.5a5.5 5.5 0 1 1 11 0 5.5 5.5 0 0 1-11 0Z"/></svg>',
  chat:
    '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 3H4a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h3v3l4-3h9a3 3 0 0 0 3-3V6a3 3 0 0 0-3-3Z"/></svg>',
};

let currentMessagesKey = null;

function safeParse(value, fallback) {
  try {
    return value ? JSON.parse(value) : fallback;
  } catch {
    return fallback;
  }
}

function messagesStorageKey(userId) {
  return `${STORAGE.messages}:${userId}`;
}

function loadMessages(userId) {
  const stored = safeParse(localStorage.getItem(messagesStorageKey(userId)), {});
  const result = {};
  for (const [threadId, thread] of Object.entries(stored)) {
    if (Array.isArray(thread)) result[threadId] = thread;
  }
  return result;
}

function saveMessages(messages) {
  if (!currentMessagesKey) return;
  try {
    localStorage.setItem(currentMessagesKey, JSON.stringify(messages));
  } catch {
    showToast("فضای ذخیره‌سازی مرورگر در دسترس نیست.", "error");
  }
}

function showToast(message, type = "info", duration = 2800) {
  const region = document.getElementById("toastRegion");
  if (!region) return;

  const toast = document.createElement("div");
  toast.className = `toast ${type}`;
  const icon = document.createElement("span");
  icon.innerHTML =
    type === "error"
      ? '<svg viewBox="0 0 24 24"><path d="M12 3 2 21h20L12 3Zm-1 6h2v6h-2V9Zm0 8h2v2h-2v-2Z"/></svg>'
      : svgIcons.check;
  const text = document.createElement("p");
  text.style.margin = "0";
  text.textContent = message;
  toast.append(icon, text);
  region.append(toast);

  window.setTimeout(() => {
    toast.classList.add("leaving");
    window.setTimeout(() => toast.remove(), 220);
  }, duration);
}

function formatClock(date = new Date()) {
  return new Intl.DateTimeFormat("fa-IR", {
    hour: "2-digit",
    minute: "2-digit",
  }).format(date);
}

function formatFileSize(bytes) {
  if (!Number.isFinite(bytes)) return "فایل";
  if (bytes < 1024) return `${bytes.toLocaleString("fa-IR")} بایت`;
  if (bytes < 1024 ** 2) {
    return `${(bytes / 1024).toLocaleString("fa-IR", { maximumFractionDigits: 1 })} کیلوبایت`;
  }
  return `${(bytes / 1024 ** 2).toLocaleString("fa-IR", { maximumFractionDigits: 1 })} مگابایت`;
}

function initLogin() {
  const form = document.getElementById("loginForm");
  if (!form) return;

  const username = document.getElementById("username");
  const password = document.getElementById("password");
  const displayName = document.getElementById("displayName");
  const displayNameField = document.getElementById("displayNameField");
  const remember = document.getElementById("remember");
  const submitButton = form.querySelector(".login-button");
  const submitLabel = submitButton.querySelector("span");
  const toggle = form.querySelector(".password-toggle");
  const headingTitle = document.getElementById("authTitle");
  const headingSubtitle = document.getElementById("authSubtitle");
  const switchButton = document.getElementById("signupButton");
  const switchLine = document.getElementById("switchLine");

  let mode = "login";

  const existingSession = getSession();
  if (existingSession) {
    username.value = existingSession.user.username;
    API.session(existingSession.token)
      .then(() => {
        window.location.href = "/chat.html";
      })
      .catch(() => clearSession());
  }

  const setError = (input, message) => {
    const field = input.closest(".field");
    const error = field.querySelector(".field-error");
    field.classList.toggle("has-error", Boolean(message));
    error.textContent = message;
    input.setAttribute("aria-invalid", message ? "true" : "false");
  };

  const setMode = (nextMode) => {
    mode = nextMode;
    const isRegister = mode === "register";
    displayNameField.hidden = !isRegister;
    displayName.required = isRegister;
    headingTitle.textContent = isRegister ? "خوش آمدی!" : "خوش برگشتی!";
    headingSubtitle.textContent = isRegister
      ? "یک حساب تازه بساز تا در فهرست مخاطبان دیده شوی."
      : "برای ادامه وارد حساب خودت شو.";
    submitLabel.textContent = isRegister ? "ساخت حساب" : "ورود به کبالت";
    switchLine.firstChild.textContent = isRegister ? "حساب داری؟ " : "هنوز عضو نیستی؟ ";
    switchButton.textContent = isRegister ? "ورود به حساب" : "ساخت حساب تازه";
    password.autocomplete = isRegister ? "new-password" : "current-password";
    setError(username, "");
    setError(password, "");
  };

  const validate = () => {
    const cleanName = username.value.trim();
    let valid = true;

    if (!/^[a-zA-Z0-9_.]{3,32}$/.test(cleanName)) {
      setError(username, "نام کاربری باید ۳ تا ۳۲ کاراکتر انگلیسی، عدد، نقطه یا زیرخط باشد.");
      valid = false;
    } else {
      setError(username, "");
    }

    if (password.value.length < 4) {
      setError(password, "رمز عبور باید حداقل ۴ کاراکتر باشد.");
      valid = false;
    } else {
      setError(password, "");
    }
    return valid;
  };

  username.addEventListener("input", () => {
    if (username.value.trim().length >= 3) setError(username, "");
  });
  password.addEventListener("input", () => {
    if (password.value.length >= 4) setError(password, "");
  });

  toggle.addEventListener("click", () => {
    const show = password.type === "password";
    password.type = show ? "text" : "password";
    toggle.setAttribute("aria-label", show ? "پنهان کردن رمز عبور" : "نمایش رمز عبور");
    toggle.style.color = show ? "var(--cobalt-500)" : "";
  });

  const authenticate = async (credentials, wantedMode = mode) => {
    submitButton.classList.add("loading");
    submitButton.disabled = true;
    try {
      const result =
        wantedMode === "register"
          ? await API.register(credentials)
          : await API.login(credentials);
      setSession({
        token: result.token,
        user: result.user,
        remember: remember.checked,
        loggedInAt: Date.now(),
      });
      showToast(
        wantedMode === "register" ? "حساب ساخته شد؛ خوش آمدی!" : "خوش برگشتی!",
        "success",
        1200,
      );
      window.setTimeout(() => {
        window.location.href = "/chat.html";
      }, 400);
    } catch (error) {
      setError(password, error.message);
      submitButton.classList.remove("loading");
      submitButton.disabled = false;
    }
  };

  form.addEventListener("submit", (event) => {
    event.preventDefault();
    if (!validate()) return;
    const credentials = {
      username: username.value.trim().toLocaleLowerCase("en-US"),
      password: password.value,
    };
    if (mode === "register") credentials.name = displayName.value.trim() || credentials.username;
    authenticate(credentials);
  });

  switchButton?.addEventListener("click", () => {
    setMode(mode === "login" ? "register" : "login");
    (mode === "register" ? displayName : username).focus();
  });

  document.getElementById("demoLogin")?.addEventListener("click", async () => {
    const credentials = {
      username: `guest${Math.floor(Math.random() * 9000 + 1000)}`,
      password: "demo1234",
    };
    credentials.name = `مهمان ${credentials.username.replace("guest", "")}`;
    username.value = credentials.username;
    password.value = credentials.password;
    await authenticate(credentials, "register");
  });

  document.getElementById("forgotPassword")?.addEventListener("click", () => {
    showToast("برای بازیابی رمز با مدیر سرور تماس بگیر.");
  });

  setMode("login");
}

function initChat() {
  const contactsList = document.getElementById("contactsList");
  if (!contactsList) return;

  const elements = {
    contactsList,
    contactSearch: document.getElementById("contactSearch"),
    messagesList: document.getElementById("messagesList"),
    messagesScroll: document.getElementById("messagesScroll"),
    messageForm: document.getElementById("messageForm"),
    messageInput: document.getElementById("messageInput"),
    sendButton: document.getElementById("sendButton"),
    voiceButton: document.getElementById("voiceButton"),
    emojiButton: document.getElementById("emojiButton"),
    emojiPicker: document.getElementById("emojiPicker"),
    emojiGrid: document.getElementById("emojiGrid"),
    fileInput: document.getElementById("fileInput"),
    filePreview: document.getElementById("filePreview"),
    filePreviewName: document.getElementById("filePreviewName"),
    filePreviewSize: document.getElementById("filePreviewSize"),
    typingRow: document.getElementById("typingRow"),
    typingName: document.getElementById("typingName"),
    sidebar: document.getElementById("sidebar"),
    overlay: document.getElementById("mobileOverlay"),
    infoPanel: document.getElementById("infoPanel"),
    infoButton: document.getElementById("infoButton"),
    accountMenu: document.getElementById("accountMenu"),
    conversationSearchBar: document.getElementById("conversationSearchBar"),
    conversationSearch: document.getElementById("conversationSearch"),
    searchResultCount: document.getElementById("searchResultCount"),
    replyPreview: document.getElementById("replyPreview"),
    replyName: document.getElementById("replyName"),
    replyText: document.getElementById("replyText"),
    scrollBottom: document.getElementById("scrollBottom"),
    confirmModal: document.getElementById("confirmModal"),
    confirmTitle: document.getElementById("confirmTitle"),
    confirmDescription: document.getElementById("confirmDescription"),
    confirmAction: document.getElementById("confirmAction"),
  };

  const session = getSession();
  if (!session) {
    window.location.href = "/";
    return;
  }

  const currentUserId = session.user.id;
  const displayName = session.user.name || session.user.username;
  currentMessagesKey = messagesStorageKey(currentUserId);

  const emptyContact = {
    id: null,
    name: "بدون مخاطب",
    initial: "؟",
    color: "avatar-blue",
    status: "offline",
    statusText: "هنوز کاربر دیگری ثبت‌نام نکرده است",
    handle: "—",
    last: "",
    time: "",
    unread: 0,
    group: false,
  };

  let visibleContacts = [];

  const state = {
    messages: loadMessages(currentUserId),
    activeId: sessionStorage.getItem(STORAGE.activeContact) || null,
    filter: "all",
    query: "",
    conversationQuery: "",
    selectedFile: null,
    replyTo: null,
    sortAscending: false,
    confirmCallback: null,
    recordingStartedAt: 0,
    recordingTimer: null,
  };

  const getActiveContact = () =>
    visibleContacts.find((contact) => contact.id === state.activeId) ||
    visibleContacts[0] ||
    emptyContact;
  const getThreadKey = (contactId = state.activeId) => contactId;
  const getActiveMessages = () => state.messages[getThreadKey()] || [];
  const getMessageDirection = (message) =>
    message?.authorId === currentUserId || message?.from === currentUserId
      ? "outgoing"
      : "incoming";

  const previewOf = (message) => {
    if (!message) return "گفت‌وگو را شروع کن";
    if (message.type === "file") return `فایل: ${message.fileName}`;
    if (message.type === "voice") return "پیام صوتی";
    return (message.text || "").replace(/\s+/g, " ").slice(0, 48);
  };

  function refreshPreview(contact) {
    const thread = state.messages[contact.id] || [];
    const latest = thread.at(-1);
    contact.last = previewOf(latest);
    contact.time = latest?.time || "";
    contact.mine = latest ? getMessageDirection(latest) === "outgoing" : false;
  }

  function applyDirectory(users = []) {
    const previous = new Map(visibleContacts.map((contact) => [contact.id, contact]));
    visibleContacts = users
      .filter((user) => user.id !== currentUserId)
      .map((user) => {
        const existing = previous.get(user.id) || {};
        const contact = {
          ...existing,
          id: user.id,
          name: user.name || user.username,
          initial: user.initial || (user.name || user.username).charAt(0),
          color: user.color || "avatar-blue",
          handle: `@${user.username}`,
          status: user.online ? "online" : "offline",
          statusText: user.online ? "آنلاین" : "آفلاین",
          group: false,
          unread: existing.unread || 0,
        };
        refreshPreview(contact);
        return contact;
      });

    if (!visibleContacts.some((contact) => contact.id === state.activeId)) {
      state.activeId = visibleContacts[0]?.id || null;
      if (state.activeId) sessionStorage.setItem(STORAGE.activeContact, state.activeId);
    }

    renderContacts();
    updateContactDetails();
    renderMessages({ preserveScroll: true });
  }

  function applyServerMessages(list = []) {
    const threads = {};
    for (const message of list) {
      const peerId = message.from === currentUserId ? message.to : message.from;
      (threads[peerId] ||= []).push(message);
    }
    for (const thread of Object.values(threads)) {
      thread.sort((first, second) => (first.createdAt || 0) - (second.createdAt || 0));
    }
    state.messages = threads;
    saveMessages(state.messages);
    for (const contact of visibleContacts) refreshPreview(contact);
    renderContacts();
    renderMessages();
  }

  document.getElementById("currentUserName").textContent = displayName;
  document.getElementById("currentUserInitial").textContent = displayName.charAt(0);

  const theme = localStorage.getItem(STORAGE.theme);
  if (theme === "dark") document.body.classList.add("dark");
  let chatSocket = null;
  let reconnectTimer = null;
  let allowReconnect = true;
  let typingTimer = null;
  let typingSentAt = 0;
  const pendingNetworkPayloads = [];

  function renderContacts() {
    const normalized = state.query.trim().toLocaleLowerCase("fa");
    let list = visibleContacts.filter((contact) => {
      const matchesQuery =
        !normalized ||
        contact.name.toLocaleLowerCase("fa").includes(normalized) ||
        contact.last.toLocaleLowerCase("fa").includes(normalized);
      const matchesFilter =
        state.filter === "all" ||
        (state.filter === "unread" && contact.unread > 0) ||
        (state.filter === "groups" && contact.group);
      return matchesQuery && matchesFilter;
    });

    if (state.sortAscending) list = [...list].reverse();
    elements.contactsList.replaceChildren();

    if (!list.length) {
      const empty = document.createElement("div");
      empty.className = "empty-contacts";
      empty.innerHTML = `<div>${svgIcons.search}<span>گفت‌وگویی پیدا نشد.</span></div>`;
      elements.contactsList.append(empty);
      return;
    }

    for (const contact of list) {
      const button = document.createElement("button");
      button.type = "button";
      button.className = `contact-item${contact.id === state.activeId ? " active" : ""}`;
      button.dataset.contactId = contact.id;
      button.setAttribute("aria-current", contact.id === state.activeId ? "true" : "false");

      const avatar = document.createElement("span");
      avatar.className = `avatar ${contact.color}`;
      const initial = document.createElement("span");
      initial.textContent = contact.initial;
      const status = document.createElement("i");
      status.className = `status-dot ${contact.status}`;
      avatar.append(initial, status);

      const main = document.createElement("span");
      main.className = "contact-main";
      const firstLine = document.createElement("span");
      firstLine.className = "contact-line";
      const name = document.createElement("strong");
      name.textContent = contact.name;
      const time = document.createElement("small");
      time.className = "contact-time";
      time.textContent = contact.time;
      firstLine.append(name, time);

      const secondLine = document.createElement("span");
      secondLine.className = "contact-line";
      const preview = document.createElement("span");
      preview.className = `contact-preview${contact.mine ? " you" : ""}`;
      preview.textContent = contact.last;
      secondLine.append(preview);

      if (contact.unread) {
        const unread = document.createElement("span");
        unread.className = "unread-badge";
        unread.textContent = contact.unread.toLocaleString("fa-IR");
        secondLine.append(unread);
      } else if (contact.mine) {
        const mark = document.createElement("span");
        mark.className = "read-mark";
        mark.textContent = "✓✓";
        secondLine.append(mark);
      }

      main.append(firstLine, secondLine);
      button.append(avatar, main);
      elements.contactsList.append(button);
    }

    document.getElementById("allCount").textContent = visibleContacts.length.toLocaleString("fa-IR");
    document.getElementById("unreadCount").textContent = visibleContacts
      .filter((contact) => contact.unread > 0)
      .length.toLocaleString("fa-IR");
  }

  function updateContactDetails() {
    const contact = getActiveContact();
    const targets = [
      ["chatName", contact.name],
      ["chatStatus", contact.statusText],
      ["infoName", contact.name],
      ["infoHandle", contact.handle],
      ["typingName", `${contact.name.split(" ")[0]} در حال نوشتن است...`],
    ];

    for (const [id, text] of targets) {
      const target = document.getElementById(id);
      if (!target) continue;
      if (id === "chatStatus") {
        target.replaceChildren();
        const dot = document.createElement("span");
        target.append(dot, document.createTextNode(` ${text}`));
        target.style.color =
          contact.status === "online"
            ? "var(--success)"
            : contact.status === "away"
              ? "var(--warning)"
              : "var(--ink-faint)";
      } else {
        target.textContent = text;
      }
    }

    for (const id of ["chatAvatar", "infoAvatar"]) {
      const avatar = document.getElementById(id);
      avatar.className = `avatar ${contact.color}`;
      avatar.querySelector("span").textContent = contact.initial;
      const dot = avatar.querySelector(".status-dot");
      if (dot) dot.className = `status-dot ${contact.status}`;
    }
  }

  function appendHighlightedText(target, text, query) {
    if (!query) {
      target.textContent = text;
      return;
    }

    const normalizedText = text.toLocaleLowerCase("fa");
    const normalizedQuery = query.toLocaleLowerCase("fa");
    let start = 0;
    let index = normalizedText.indexOf(normalizedQuery, start);
    while (index >= 0) {
      target.append(document.createTextNode(text.slice(start, index)));
      const mark = document.createElement("mark");
      mark.className = "search-highlight";
      mark.textContent = text.slice(index, index + query.length);
      target.append(mark);
      start = index + query.length;
      index = normalizedText.indexOf(normalizedQuery, start);
    }
    target.append(document.createTextNode(text.slice(start)));
  }

  function createMessage(message, index, list) {
    const contact = getActiveContact();
    const row = document.createElement("article");
    const previous = list[index - 1];
    const direction = getMessageDirection(message);
    const grouped = previous && getMessageDirection(previous) === direction;
    row.className = `message-row ${direction}${grouped ? " grouped" : ""}`;
    row.dataset.messageId = message.id;

    if (direction === "incoming") {
      const avatar = document.createElement("span");
      avatar.className = `avatar message-avatar ${contact.color}`;
      avatar.textContent = contact.initial;
      row.append(avatar);
    }

    const wrap = document.createElement("div");
    wrap.className = "message-wrap";
    const bubble = document.createElement("div");
    bubble.className = "message-bubble";

    if (message.reply) {
      const quote = document.createElement("div");
      quote.className = "reply-quote";
      const quoteName = document.createElement("b");
      quoteName.textContent = message.reply.name;
      const quoteText = document.createElement("span");
      quoteText.textContent = message.reply.text;
      quote.append(quoteName, quoteText);
      bubble.append(quote);
    }

    if (message.type === "file") {
      const file = document.createElement("div");
      file.className = "file-bubble";
      const fileIcon = document.createElement("span");
      fileIcon.innerHTML = svgIcons.file;
      const fileData = document.createElement("span");
      const fileName = document.createElement("b");
      fileName.textContent = message.fileName;
      const fileSize = document.createElement("small");
      fileSize.textContent = message.fileSize;
      fileData.append(fileName, fileSize);
      const download = document.createElement("button");
      download.type = "button";
      download.className = "file-download";
      download.dataset.downloadMessage = message.id;
      download.setAttribute("aria-label", "دریافت فایل نمایشی");
      download.innerHTML = svgIcons.download;
      file.append(fileIcon, fileData, download);
      bubble.append(file);
    } else if (message.type === "voice") {
      const voice = document.createElement("div");
      voice.className = "file-bubble";
      const voiceIcon = document.createElement("span");
      voiceIcon.innerHTML =
        '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7L8 5Z"/></svg>';
      const voiceData = document.createElement("span");
      const voiceTitle = document.createElement("b");
      voiceTitle.textContent = "پیام صوتی";
      const voiceDuration = document.createElement("small");
      voiceDuration.textContent = message.duration || "۰۰:۰۴";
      voiceData.append(voiceTitle, voiceDuration);
      voice.append(voiceIcon, voiceData);
      bubble.append(voice);
    } else {
      const paragraph = document.createElement("p");
      appendHighlightedText(paragraph, message.text || "", state.conversationQuery);
      bubble.append(paragraph);
    }

    const meta = document.createElement("span");
    meta.className = "message-meta";
    const time = document.createElement("time");
    time.textContent = message.time;
    meta.append(time);
    if (direction === "outgoing") {
      const checks = document.createElement("span");
      checks.className = "message-checks";
      checks.textContent = message.read === false ? "✓" : "✓✓";
      meta.append(checks);
    }
    bubble.append(meta);

    const actions = document.createElement("div");
    actions.className = "message-actions";
    const replyButton = document.createElement("button");
    replyButton.type = "button";
    replyButton.dataset.replyMessage = message.id;
    replyButton.setAttribute("aria-label", "پاسخ");
    replyButton.innerHTML = svgIcons.reply;
    const reactionButton = document.createElement("button");
    reactionButton.type = "button";
    reactionButton.dataset.reactMessage = message.id;
    reactionButton.setAttribute("aria-label", "واکنش");
    reactionButton.innerHTML = svgIcons.smile;
    actions.append(replyButton, reactionButton);

    wrap.append(bubble, actions);
    if (message.reaction) {
      const reaction = document.createElement("span");
      reaction.className = "message-reaction";
      reaction.textContent = message.reaction;
      const count = document.createElement("small");
      count.textContent = "۱";
      reaction.append(count);
      wrap.append(reaction);
    }
    row.append(wrap);
    return row;
  }

  function renderMessages({ preserveScroll = false } = {}) {
    const previousDistance =
      elements.messagesScroll.scrollHeight -
      elements.messagesScroll.scrollTop -
      elements.messagesScroll.clientHeight;
    const list = getActiveMessages();
    elements.messagesList.replaceChildren();

    const divider = document.createElement("div");
    divider.className = "date-divider";
    divider.textContent = "امروز";
    elements.messagesList.append(divider);

    if (!list.length) {
      const empty = document.createElement("div");
      empty.className = "empty-conversation";
      empty.innerHTML = `<div><span>${svgIcons.chat}</span><h3>شروع یک گفت‌وگوی تازه</h3><p>اولین پیام را برای ${getActiveContact().name} بفرست.</p></div>`;
      elements.messagesList.append(empty);
    } else {
      list.forEach((message, index) => {
        elements.messagesList.append(createMessage(message, index, list));
      });
    }

    if (state.conversationQuery) {
      const matches = list.filter((message) =>
        (message.text || "")
          .toLocaleLowerCase("fa")
          .includes(state.conversationQuery.toLocaleLowerCase("fa")),
      ).length;
      elements.searchResultCount.textContent = matches
        ? `${matches.toLocaleString("fa-IR")} نتیجه`
        : "بدون نتیجه";
    } else {
      elements.searchResultCount.textContent = "";
    }

    requestAnimationFrame(() => {
      if (preserveScroll) {
        elements.messagesScroll.scrollTop =
          elements.messagesScroll.scrollHeight -
          elements.messagesScroll.clientHeight -
          previousDistance;
      } else {
        scrollToBottom(false);
      }
    });
  }

  function scrollToBottom(smooth = true) {
    elements.messagesScroll.scrollTo({
      top: elements.messagesScroll.scrollHeight,
      behavior: smooth ? "smooth" : "auto",
    });
    elements.scrollBottom.classList.remove("visible");
  }

  function selectContact(contactId) {
    if (!visibleContacts.some((contact) => contact.id === contactId)) return;
    elements.typingRow.hidden = true;
    state.activeId = contactId;
    sessionStorage.setItem(STORAGE.activeContact, contactId);
    const contact = getActiveContact();
    contact.unread = 0;
    state.conversationQuery = "";
    elements.conversationSearch.value = "";
    elements.conversationSearchBar.hidden = true;
    elements.typingRow.hidden = true;
    clearComposerExtras();
    updateContactDetails();
    renderContacts();
    renderMessages();
    closeMobilePanels();
    document.title = `${contact.name} | کبالت`;
  }

  function updateComposer() {
    elements.messageInput.style.height = "auto";
    elements.messageInput.style.height = `${Math.min(elements.messageInput.scrollHeight, 110)}px`;
    const hasContent = Boolean(
      elements.messageInput.value.trim() || state.selectedFile || state.replyTo,
    );
    elements.sendButton.hidden = !hasContent;
    elements.voiceButton.hidden = hasContent;
  }

  function clearComposerExtras() {
    state.selectedFile = null;
    state.replyTo = null;
    elements.fileInput.value = "";
    elements.filePreview.hidden = true;
    elements.replyPreview.hidden = true;
    updateComposer();
  }

  function storeMessage(message) {
    const peerId = message.from === currentUserId ? message.to : message.from;
    if (!peerId) return null;
    const thread = (state.messages[peerId] ||= []);
    if (thread.some((item) => item.id === message.id)) return peerId;
    thread.push(message);
    saveMessages(state.messages);

    const contact = visibleContacts.find((item) => item.id === peerId);
    if (contact) {
      refreshPreview(contact);
      if (message.from !== currentUserId && state.activeId !== peerId) {
        contact.unread = (contact.unread || 0) + 1;
      }
    }
    return peerId;
  }

  function addMessage(message) {
    const peerId = state.activeId;
    if (!peerId) {
      showToast("ابتدا یک مخاطب را انتخاب کن.", "error");
      return;
    }
    const outgoing = { ...message, from: currentUserId, to: peerId };
    storeMessage(outgoing);
    renderContacts();
    renderMessages();
    sendNetwork({ type: "message", to: peerId, message: outgoing });
  }

  function sendNetwork(payload) {
    if (chatSocket?.readyState === WebSocket.OPEN) {
      chatSocket.send(JSON.stringify(payload));
      return;
    }
    pendingNetworkPayloads.push(payload);
  }

  function notifyTyping(active) {
    if (!state.activeId) return;
    const now = Date.now();
    if (active && now - typingSentAt < 1500) return;
    typingSentAt = active ? now : 0;
    sendNetwork({ type: "typing", to: state.activeId, active });
  }

  function showPeerTyping(peerId, active) {
    if (peerId !== state.activeId) return;
    window.clearTimeout(typingTimer);
    elements.typingRow.hidden = !active;
    if (active) {
      typingTimer = window.setTimeout(() => {
        elements.typingRow.hidden = true;
      }, 4000);
    }
  }

  function connectNetworkChat() {
    window.clearTimeout(reconnectTimer);
    const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
    chatSocket = new WebSocket(
      `${protocol}//${window.location.host}/ws?token=${encodeURIComponent(session.token)}`,
    );

    chatSocket.addEventListener("open", () => {
      while (pendingNetworkPayloads.length && chatSocket.readyState === WebSocket.OPEN) {
        chatSocket.send(JSON.stringify(pendingNetworkPayloads.shift()));
      }
    });

    chatSocket.addEventListener("message", (event) => {
      let payload;
      try {
        payload = JSON.parse(event.data);
      } catch {
        return;
      }

      if (payload.type === "init") {
        applyServerMessages(payload.messages || []);
        applyDirectory(payload.users || []);
        return;
      }

      if (payload.type === "directory") {
        applyDirectory(payload.users || []);
        return;
      }

      if (payload.type === "message" && payload.message) {
        const peerId = storeMessage(payload.message);
        renderContacts();
        if (peerId === state.activeId) renderMessages();
        if (payload.message.from !== currentUserId) {
          showPeerTyping(payload.message.from, false);
          const sender = visibleContacts.find((item) => item.id === payload.message.from);
          if (peerId !== state.activeId) {
            showToast(`پیام جدید از ${sender?.name || "مخاطب"}`, "success");
          }
        }
        return;
      }

      if (payload.type === "reaction") {
        for (const thread of Object.values(state.messages)) {
          const message = thread.find((item) => item.id === payload.messageId);
          if (message) {
            message.reaction = payload.reaction || undefined;
            saveMessages(state.messages);
            renderMessages({ preserveScroll: true });
            break;
          }
        }
        return;
      }

      if (payload.type === "typing") {
        showPeerTyping(payload.from, payload.active);
        return;
      }

      if (payload.type === "clear") {
        const peerId = payload.from === currentUserId ? payload.to : payload.from;
        state.messages[peerId] = [];
        saveMessages(state.messages);
        const contact = visibleContacts.find((item) => item.id === peerId);
        if (contact) refreshPreview(contact);
        renderContacts();
        if (peerId === state.activeId) renderMessages();
      }
    });

    chatSocket.addEventListener("close", (event) => {
      chatSocket = null;
      if (event.code === 1008) {
        clearSession();
        window.location.href = "/";
        return;
      }
      if (allowReconnect) {
        reconnectTimer = window.setTimeout(connectNetworkChat, 1500);
      }
    });
  }

  function sendCurrentMessage() {
    const text = elements.messageInput.value.trim();
    if (!text && !state.selectedFile) return;

    const base = {
      id: `${state.activeId}-${Date.now()}`,
      time: formatClock(),
      read: true,
    };

    if (state.replyTo) {
      base.reply = {
        name:
          getMessageDirection(state.replyTo) === "outgoing"
            ? displayName.split(" ")[0]
            : getActiveContact().name.split(" ")[0],
        text:
          state.replyTo.text ||
          state.replyTo.fileName ||
          (state.replyTo.type === "voice" ? "پیام صوتی" : "پیام"),
      };
    }

    if (state.selectedFile) {
      addMessage({
        ...base,
        type: "file",
        fileName: state.selectedFile.name,
        fileSize: `${formatFileSize(state.selectedFile.size)} · ${(
          state.selectedFile.name.split(".").pop() || "FILE"
        ).toUpperCase()}`,
      });
      if (text) {
        window.setTimeout(() => {
          addMessage({
            ...base,
            id: `${state.activeId}-${Date.now()}-text`,
            text,
            reply: undefined,
          });
        }, 80);
      }
    } else {
      addMessage({ ...base, text });
    }

    elements.messageInput.value = "";
    clearComposerExtras();
    elements.messageInput.focus();
    notifyTyping(false);
  }

  function closeMobilePanels() {
    elements.sidebar.classList.remove("open");
    if (window.innerWidth <= 860) {
      elements.infoPanel.classList.remove("open");
      elements.infoPanel.setAttribute("aria-hidden", "true");
      elements.infoButton.setAttribute("aria-expanded", "false");
    }
    elements.overlay.classList.remove("visible");
  }

  function toggleInfo(force) {
    const open =
      typeof force === "boolean" ? force : !elements.infoPanel.classList.contains("open");
    elements.infoPanel.classList.toggle("open", open);
    elements.infoPanel.setAttribute("aria-hidden", String(!open));
    elements.infoButton.setAttribute("aria-expanded", String(open));
    if (window.innerWidth <= 860) {
      elements.overlay.classList.toggle("visible", open);
    }
  }

  function openConfirm({ title, description, actionLabel = "تأیید", onConfirm }) {
    elements.confirmTitle.textContent = title;
    elements.confirmDescription.textContent = description;
    elements.confirmAction.textContent = actionLabel;
    state.confirmCallback = onConfirm;
    elements.confirmModal.hidden = false;
    requestAnimationFrame(() => document.getElementById("confirmCancel").focus());
  }

  function closeConfirm() {
    elements.confirmModal.hidden = true;
    state.confirmCallback = null;
  }

  elements.contactsList.addEventListener("click", (event) => {
    const button = event.target.closest("[data-contact-id]");
    if (button) selectContact(button.dataset.contactId);
  });

  elements.contactSearch.addEventListener("input", () => {
    state.query = elements.contactSearch.value;
    renderContacts();
  });

  document.querySelector(".list-tabs").addEventListener("click", (event) => {
    const tab = event.target.closest("[data-filter]");
    if (!tab) return;
    state.filter = tab.dataset.filter;
    document
      .querySelectorAll(".list-tabs button")
      .forEach((button) => button.classList.toggle("active", button === tab));
    renderContacts();
  });

  document.getElementById("sortContacts").addEventListener("click", (event) => {
    state.sortAscending = !state.sortAscending;
    event.currentTarget.childNodes[0].textContent = state.sortAscending
      ? "قدیمی‌ترین "
      : "تازه‌ترین ";
    renderContacts();
  });

  elements.messageInput.addEventListener("input", () => {
    updateComposer();
    notifyTyping(Boolean(elements.messageInput.value.trim()));
  });
  elements.messageInput.addEventListener("keydown", (event) => {
    if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
      event.preventDefault();
      sendCurrentMessage();
    }
  });

  elements.messageForm.addEventListener("submit", (event) => {
    event.preventDefault();
    sendCurrentMessage();
  });

  const emojis = [
    "😀",
    "😂",
    "😍",
    "🥰",
    "😊",
    "😎",
    "🤩",
    "🥳",
    "😉",
    "🤔",
    "🙌",
    "👏",
    "👍",
    "👌",
    "🤝",
    "💪",
    "❤️",
    "💙",
    "✨",
    "🔥",
    "🎉",
  ];
  for (const emoji of emojis) {
    const button = document.createElement("button");
    button.type = "button";
    button.textContent = emoji;
    button.setAttribute("aria-label", `افزودن ${emoji}`);
    button.addEventListener("click", () => {
      const start = elements.messageInput.selectionStart;
      const end = elements.messageInput.selectionEnd;
      elements.messageInput.setRangeText(emoji, start, end, "end");
      elements.emojiPicker.hidden = true;
      elements.messageInput.focus();
      updateComposer();
    });
    elements.emojiGrid.append(button);
  }

  elements.emojiButton.addEventListener("click", (event) => {
    event.stopPropagation();
    elements.emojiPicker.hidden = !elements.emojiPicker.hidden;
    elements.accountMenu.hidden = true;
  });

  elements.fileInput.addEventListener("change", () => {
    const file = elements.fileInput.files?.[0];
    if (!file) return;
    const maxSize = 20 * 1024 * 1024;
    if (file.size > maxSize) {
      elements.fileInput.value = "";
      showToast("حداکثر اندازه فایل نمایشی ۲۰ مگابایت است.", "error");
      return;
    }
    state.selectedFile = file;
    elements.filePreviewName.textContent = file.name;
    elements.filePreviewSize.textContent = formatFileSize(file.size);
    elements.filePreview.hidden = false;
    updateComposer();
  });

  document.getElementById("removeFile").addEventListener("click", () => {
    state.selectedFile = null;
    elements.fileInput.value = "";
    elements.filePreview.hidden = true;
    updateComposer();
  });

  elements.messagesList.addEventListener("click", (event) => {
    const replyButton = event.target.closest("[data-reply-message]");
    const reactionButton = event.target.closest("[data-react-message]");
    const downloadButton = event.target.closest("[data-download-message]");

    if (downloadButton) {
      showToast("دریافت فایل در نسخه نمایشی شبیه‌سازی شد.", "success");
      return;
    }

    if (replyButton) {
      const message = getActiveMessages().find(
        (item) => item.id === replyButton.dataset.replyMessage,
      );
      if (!message) return;
      state.replyTo = message;
      elements.replyName.textContent =
        getMessageDirection(message) === "outgoing"
          ? displayName.split(" ")[0]
          : getActiveContact().name.split(" ")[0];
      elements.replyText.textContent =
        message.text || message.fileName || (message.type === "voice" ? "پیام صوتی" : "پیام");
      elements.replyPreview.hidden = false;
      elements.messageInput.focus();
      updateComposer();
    }

    if (reactionButton) {
      const message = getActiveMessages().find(
        (item) => item.id === reactionButton.dataset.reactMessage,
      );
      if (!message) return;
      message.reaction = message.reaction ? undefined : "💙";
      saveMessages(state.messages);
      sendNetwork({
        type: "reaction",
        messageId: message.id,
        reaction: message.reaction,
      });
      renderMessages({ preserveScroll: true });
    }
  });

  document.getElementById("cancelReply").addEventListener("click", () => {
    state.replyTo = null;
    elements.replyPreview.hidden = true;
    updateComposer();
  });

  elements.voiceButton.addEventListener("click", () => {
    if (!state.recordingStartedAt) {
      state.recordingStartedAt = Date.now();
      elements.voiceButton.classList.add("recording");
      elements.voiceButton.setAttribute("aria-label", "پایان ضبط و ارسال");
      showToast("ضبط نمایشی آغاز شد؛ برای ارسال دوباره بزن.");
      state.recordingTimer = window.setTimeout(() => {
        if (state.recordingStartedAt) elements.voiceButton.click();
      }, 12000);
      return;
    }

    const seconds = Math.max(1, Math.round((Date.now() - state.recordingStartedAt) / 1000));
    window.clearTimeout(state.recordingTimer);
    state.recordingStartedAt = 0;
    elements.voiceButton.classList.remove("recording");
    elements.voiceButton.setAttribute("aria-label", "ضبط پیام صوتی");
    addMessage({
      id: `${state.activeId}-${Date.now()}`,
      type: "voice",
      duration: `۰۰:${String(seconds).padStart(2, "0")}`.replace(/\d/g, (digit) => "۰۱۲۳۴۵۶۷۸۹"[digit]),
      time: formatClock(),
      read: true,
    });
  });

  document.getElementById("openSidebar").addEventListener("click", () => {
    elements.sidebar.classList.add("open");
    elements.overlay.classList.add("visible");
  });
  document.getElementById("closeSidebar").addEventListener("click", closeMobilePanels);
  elements.overlay.addEventListener("click", closeMobilePanels);

  elements.infoButton.addEventListener("click", () => toggleInfo());
  document.getElementById("closeInfo").addEventListener("click", () => toggleInfo(false));

  document.getElementById("conversationSearchButton").addEventListener("click", () => {
    elements.conversationSearchBar.hidden = false;
    requestAnimationFrame(() => elements.conversationSearch.focus());
  });
  document.getElementById("closeConversationSearch").addEventListener("click", () => {
    state.conversationQuery = "";
    elements.conversationSearch.value = "";
    elements.conversationSearchBar.hidden = true;
    renderMessages({ preserveScroll: true });
  });
  elements.conversationSearch.addEventListener("input", () => {
    state.conversationQuery = elements.conversationSearch.value.trim();
    renderMessages({ preserveScroll: true });
  });

  elements.messagesScroll.addEventListener("scroll", () => {
    const distance =
      elements.messagesScroll.scrollHeight -
      elements.messagesScroll.scrollTop -
      elements.messagesScroll.clientHeight;
    elements.scrollBottom.classList.toggle("visible", distance > 170);
  });
  elements.scrollBottom.addEventListener("click", () => scrollToBottom());

  document.getElementById("userMenuButton").addEventListener("click", (event) => {
    event.stopPropagation();
    elements.accountMenu.hidden = !elements.accountMenu.hidden;
    elements.emojiPicker.hidden = true;
  });

  elements.accountMenu.addEventListener("click", (event) => {
    const action = event.target.closest("[data-action]")?.dataset.action;
    if (!action) return;
    elements.accountMenu.hidden = true;
    if (action === "theme") {
      document.body.classList.toggle("dark");
      localStorage.setItem(
        STORAGE.theme,
        document.body.classList.contains("dark") ? "dark" : "light",
      );
      showToast(
        document.body.classList.contains("dark") ? "حالت شب فعال شد." : "حالت روشن فعال شد.",
        "success",
      );
    } else if (action === "profile") {
      showToast("پروفایل شما در نسخه نمایشی آماده ویرایش است.");
    } else if (action === "logout") {
      openConfirm({
        title: "خروج از حساب",
        description: "می‌خواهی از حساب کبالت خارج شوی؟ گفت‌وگوها روی سرور باقی می‌مانند.",
        actionLabel: "خروج",
        onConfirm: () => {
          allowReconnect = false;
          chatSocket?.close();
          sessionStorage.removeItem(STORAGE.activeContact);
          clearSession();
          window.location.href = "/";
        },
      });
    }
  });

  document.addEventListener("click", (event) => {
    if (!event.target.closest("#emojiPicker, #emojiButton")) {
      elements.emojiPicker.hidden = true;
    }
    if (!event.target.closest("#accountMenu, #userMenuButton")) {
      elements.accountMenu.hidden = true;
    }
  });

  document.addEventListener("keydown", (event) => {
    if ((event.metaKey || event.ctrlKey) && event.key.toLocaleLowerCase() === "k") {
      event.preventDefault();
      if (window.innerWidth <= 860) {
        elements.sidebar.classList.add("open");
        elements.overlay.classList.add("visible");
      }
      elements.contactSearch.focus();
    }
    if (event.key === "Escape") {
      elements.emojiPicker.hidden = true;
      elements.accountMenu.hidden = true;
      if (!elements.confirmModal.hidden) closeConfirm();
      else if (!elements.conversationSearchBar.hidden) {
        document.getElementById("closeConversationSearch").click();
      } else {
        closeMobilePanels();
        toggleInfo(false);
      }
    }
  });

  document.getElementById("confirmCancel").addEventListener("click", closeConfirm);
  elements.confirmAction.addEventListener("click", () => {
    const callback = state.confirmCallback;
    closeConfirm();
    callback?.();
  });
  elements.confirmModal.addEventListener("click", (event) => {
    if (event.target === elements.confirmModal) closeConfirm();
  });

  document.getElementById("clearChatButton").addEventListener("click", () => {
    const contact = getActiveContact();
    openConfirm({
      title: "پاک کردن گفت‌وگو",
      description: `همه پیام‌های گفت‌وگو با ${contact.name} برای هر دو طرف حذف شوند؟`,
      actionLabel: "پاک کردن",
      onConfirm: () => {
        if (!contact.id) return;
        state.messages[contact.id] = [];
        saveMessages(state.messages);
        sendNetwork({ type: "clear", to: contact.id });
        contact.last = "گفت‌وگو پاک شد";
        contact.time = "اکنون";
        renderContacts();
        renderMessages();
        toggleInfo(false);
        showToast("گفت‌وگو پاک شد.", "success");
      },
    });
  });

  document.getElementById("newChatButton").addEventListener("click", () => {
    state.filter = "all";
    document.querySelector('[data-filter="all"]').click();
    elements.contactSearch.value = "";
    state.query = "";
    renderContacts();
    elements.contactSearch.focus();
    showToast("نام مخاطب را جست‌وجو و انتخاب کن.");
  });

  const demoActionIds = [
    "callsButton",
    "settingsButton",
    "voiceCallButton",
    "videoCallButton",
    "profileVoiceCall",
    "starredButton",
    "moreProfileButton",
  ];
  demoActionIds.forEach((id) => {
    document.getElementById(id)?.addEventListener("click", () => {
      showToast("این قابلیت در نسخه نمایشی شبیه‌سازی شده است.");
    });
  });

  document.getElementById("muteButton").addEventListener("click", (event) => {
    event.currentTarget.classList.toggle("muted");
    const muted = event.currentTarget.classList.contains("muted");
    event.currentTarget.lastChild.textContent = muted ? " باصدا" : " بی‌صدا";
    showToast(muted ? "اعلان‌های این گفت‌وگو بی‌صدا شد." : "صدای اعلان‌ها فعال شد.", "success");
  });

  document.getElementById("editStatus").addEventListener("click", () => {
    showToast("وضعیت شما: آنلاین");
  });

  window.addEventListener("storage", (event) => {
    if (event.key === STORAGE.session && !getSession()) {
      window.location.href = "/";
    }
  });

  window.addEventListener(
    "pagehide",
    () => {
      allowReconnect = false;
      window.clearTimeout(reconnectTimer);
      chatSocket?.close();
    },
    { once: true },
  );

  window.addEventListener("resize", () => {
    if (window.innerWidth > 860) {
      elements.sidebar.classList.remove("open");
      elements.overlay.classList.remove("visible");
    }
  });

  updateContactDetails();
  renderContacts();
  renderMessages();
  updateComposer();
  connectNetworkChat();
}

const page = document.body.dataset.page;
if (page === "login") initLogin();
if (page === "chat") initChat();
