import { useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import {
  addChatMember,
  banChatMember,
  createInviteLink,
  deleteChat,
  getChatAttachments,
  getMessages,
  getChatNotificationSettings,
  getChatDetail,
  leaveChat,
  listChatBans,
  listChatMembers,
  requestUploadUrl,
  removeChatMember,
  unbanChatMember,
  updateChatProfile,
  updateChatNotificationSettings,
  updateChatMemberRole,
  uploadToPresignedUrl
} from "../api/chats";
import { blockUser, getUserById, listBlockedUsers, searchUsers, unblockUser } from "../api/users";
import type { AuthUser, ChatAttachment, ChatBan, ChatDetail, ChatMember, Message, UserSearchItem } from "../chat/types";
import { useAuthStore } from "../store/authStore";
import { useChatStore } from "../store/chatStore";
import { useUiStore } from "../store/uiStore";
import { AvatarCropModal } from "./AvatarCropModal";
import { MediaViewer } from "./MediaViewer";

interface Props {
  chatId: number | null;
  open: boolean;
  onClose: () => void;
}

export function ChatInfoPanel({ chatId, open, onClose }: Props) {
  const me = useAuthStore((s) => s.me);
  const loadChats = useChatStore((s) => s.loadChats);
  const updateChatMuted = useChatStore((s) => s.updateChatMuted);
  const setActiveChatId = useChatStore((s) => s.setActiveChatId);
  const setFocusedMessage = useChatStore((s) => s.setFocusedMessage);
  const showToast = useUiStore((s) => s.showToast);
  const [chat, setChat] = useState<ChatDetail | null>(null);
  const [members, setMembers] = useState<ChatMember[]>([]);
  const [memberUsers, setMemberUsers] = useState<Record<number, AuthUser>>({});
  const [counterpartProfile, setCounterpartProfile] = useState<AuthUser | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [titleDraft, setTitleDraft] = useState("");
  const [savingTitle, setSavingTitle] = useState(false);
  const [chatAvatarUploading, setChatAvatarUploading] = useState(false);
  const [searchQuery, setSearchQuery] = useState("");
  const [searchResults, setSearchResults] = useState<UserSearchItem[]>([]);
  const [memberFilter, setMemberFilter] = useState("");
  const [bannedFilter, setBannedFilter] = useState("");
  const [muted, setMuted] = useState(false);
  const [savingMute, setSavingMute] = useState(false);
  const [counterpartBlocked, setCounterpartBlocked] = useState(false);
  const [savingBlock, setSavingBlock] = useState(false);
  const [inviteLink, setInviteLink] = useState<string | null>(null);
  const [attachments, setAttachments] = useState<ChatAttachment[]>([]);
  const [attachmentsLoading, setAttachmentsLoading] = useState(false);
  const [linkItems, setLinkItems] = useState<Array<{ url: string; messageId: number; createdAt: string }>>([]);
  const [attachmentCtx, setAttachmentCtx] = useState<{ x: number; y: number; url: string; messageId: number } | null>(null);
  const [memberCtx, setMemberCtx] = useState<{ x: number; y: number; member: ChatMember } | null>(null);
  const [banCtx, setBanCtx] = useState<{ x: number; y: number; ban: ChatBan } | null>(null);
  const [bans, setBans] = useState<ChatBan[]>([]);
  const [bannedUsers, setBannedUsers] = useState<Record<number, AuthUser>>({});
  const [attachmentsTab, setAttachmentsTab] = useState<"all" | "photos" | "videos" | "audio" | "links" | "voice">("all");
  const [mediaViewer, setMediaViewer] = useState<{ items: Array<{ url: string; type: "image" | "video"; messageId: number }>; index: number } | null>(null);
  const [avatarCropFile, setAvatarCropFile] = useState<File | null>(null);

  const myRole = useMemo(() => {
    if (chat?.my_role) {
      return chat.my_role;
    }
    return members.find((m) => m.user_id === me?.id)?.role;
  }, [chat?.my_role, members, me?.id]);
  const myRoleNormalized = useMemo(() => {
    if (!myRole) {
      return null;
    }
    const role = String(myRole).toLowerCase();
    if (role === "owner" || role === "admin" || role === "member") {
      return role;
    }
    return null;
  }, [myRole]);
  const isGroupLike = chat?.type === "group" || chat?.type === "channel";
  const canViewMembersList = Boolean(isGroupLike && (myRoleNormalized === "owner" || myRoleNormalized === "admin"));
  const showMembersSection = Boolean(chat && isGroupLike && !chat.is_saved && canViewMembersList);
  const canManageMembers = Boolean(isGroupLike && (myRoleNormalized === "owner" || myRoleNormalized === "admin"));
  const canEditTitle = Boolean(isGroupLike && (myRoleNormalized === "owner" || myRoleNormalized === "admin"));
  const canEditChatProfile = Boolean(isGroupLike && (myRoleNormalized === "owner" || myRoleNormalized === "admin"));
  const canLeaveGroupLikeChat = useMemo(() => {
    if (!chat || !isGroupLike) {
      return false;
    }
    if (myRoleNormalized !== "owner") {
      return true;
    }
    const count = chat.members_count ?? members.length;
    return count <= 1;
  }, [chat, isGroupLike, myRoleNormalized, members.length]);
  const photoAttachments = useMemo(() => attachments.filter((item) => item.file_type.startsWith("image/")).sort((a, b) => b.id - a.id), [attachments]);
  const videoAttachments = useMemo(() => attachments.filter((item) => item.file_type.startsWith("video/")).sort((a, b) => b.id - a.id), [attachments]);
  const voiceAttachments = useMemo(() => attachments.filter((item) => item.message_type === "voice").sort((a, b) => b.id - a.id), [attachments]);
  const audioAttachments = useMemo(
    () =>
      attachments
        .filter((item) => item.message_type === "audio" || (item.file_type.startsWith("audio/") && item.message_type !== "voice"))
        .sort((a, b) => b.id - a.id),
    [attachments]
  );
  const allAttachmentItems = useMemo(() => [...attachments].sort((a, b) => b.id - a.id), [attachments]);
  const filteredMembers = useMemo(() => {
    const query = memberFilter.trim().replace(/^@+/, "").toLowerCase();
    if (!query) {
      return members;
    }
    return members.filter((member) => {
      const user = memberUsers[member.user_id];
      return (
        (user?.name ?? "").toLowerCase().includes(query) ||
        (user?.username ?? "").toLowerCase().includes(query) ||
        String(member.user_id).includes(query)
      );
    });
  }, [memberFilter, members, memberUsers]);
  const filteredBans = useMemo(() => {
    const query = bannedFilter.trim().replace(/^@+/, "").toLowerCase();
    if (!query) {
      return bans;
    }
    return bans.filter((ban) => {
      const user = bannedUsers[ban.user_id];
      return (
        (user?.name ?? "").toLowerCase().includes(query) ||
        (user?.username ?? "").toLowerCase().includes(query) ||
        String(ban.user_id).includes(query)
      );
    });
  }, [bannedFilter, bans, bannedUsers]);

  async function refreshMembers(targetChatId: number): Promise<ChatMember[]> {
    const nextMembers = await listChatMembers(targetChatId);
    setMembers(nextMembers);
    const byId: Record<number, AuthUser> = {};
    const missingIds: number[] = [];
    for (const member of nextMembers) {
      if (member.name || member.username || member.avatar_url) {
        byId[member.user_id] = {
          id: member.user_id,
          email: "",
          name: member.name || member.username || `user #${member.user_id}`,
          username: member.username || `user${member.user_id}`,
          bio: null,
          avatar_url: member.avatar_url || null,
          email_verified: false,
          allow_private_messages: true,
          created_at: "",
          updated_at: "",
        };
      } else {
        missingIds.push(member.user_id);
      }
    }
    if (missingIds.length) {
      const profiles = await Promise.allSettled(missingIds.map((id) => getUserById(id)));
      for (const profile of profiles) {
        if (profile.status === "fulfilled") {
          byId[profile.value.id] = profile.value;
        }
      }
    }
    setMemberUsers(byId);
    return nextMembers;
  }

  async function refreshBans(targetChatId: number, allowFailure = true) {
    try {
      const nextBans = await listChatBans(targetChatId);
      setBans(nextBans);
      const ids = [...new Set(nextBans.flatMap((item) => [item.user_id, item.banned_by_user_id]))];
      const profiles = await Promise.allSettled(ids.map((id) => getUserById(id)));
      const byId: Record<number, AuthUser> = {};
      for (const profile of profiles) {
        if (profile.status === "fulfilled") {
          byId[profile.value.id] = profile.value;
        }
      }
      setBannedUsers(byId);
    } catch {
      setBans([]);
      setBannedUsers({});
      if (!allowFailure) {
        throw new Error("failed_to_load_bans");
      }
    }
  }

  async function refreshPanelData(targetChatId: number, withLoading = false) {
    if (withLoading) {
      setLoading(true);
      setError(null);
      setAttachmentsLoading(true);
    }
    try {
      const detail = await getChatDetail(targetChatId);
      setChat(detail);
      setTitleDraft(detail.title ?? "");
      const notificationSettings = await getChatNotificationSettings(targetChatId);
      setMuted(notificationSettings.muted);
      if (detail.type === "private" && !detail.is_saved && detail.counterpart_user_id) {
        try {
          const counterpart = await getUserById(detail.counterpart_user_id);
          setCounterpartProfile(counterpart);
          const blocked = await listBlockedUsers();
          setCounterpartBlocked(blocked.some((u) => u.id === detail.counterpart_user_id));
        } catch {
          setCounterpartProfile(null);
          setCounterpartBlocked(false);
        }
      } else {
        setCounterpartProfile(null);
        setCounterpartBlocked(false);
      }
      const resolvedRole = String(detail.my_role ?? "").toLowerCase();
      const canLoadMembers = (detail.type === "group" || detail.type === "channel") && (resolvedRole === "owner" || resolvedRole === "admin");
      if (canLoadMembers) {
        await refreshMembers(targetChatId);
      } else {
        setMembers([]);
        setMemberUsers({});
      }
      const canLoadBans = (detail.type === "group" || detail.type === "channel") && (resolvedRole === "owner" || resolvedRole === "admin");
      if (canLoadBans) {
        await refreshBans(targetChatId);
      } else {
        setBans([]);
        setBannedUsers({});
      }
      const chatAttachments = await getChatAttachments(targetChatId, 120);
      const messages = await getRecentMessagesForLinks(targetChatId);
      setAttachments(chatAttachments);
      setLinkItems(extractLinkItems(messages));
    } catch {
      if (withLoading) {
        setError("Failed to load chat info");
      }
    } finally {
      if (withLoading) {
        setLoading(false);
        setAttachmentsLoading(false);
      }
    }
  }

  function jumpToMessage(messageId: number) {
    if (!chatId) {
      return;
    }
    setActiveChatId(chatId);
    setFocusedMessage(chatId, messageId);
    onClose();
  }

  async function uploadChatAvatar(file: File) {
    if (!chatId || !canEditChatProfile) {
      return;
    }
    setChatAvatarUploading(true);
    try {
      const upload = await requestUploadUrl(file);
      await uploadToPresignedUrl(upload.upload_url, upload.required_headers, file);
      const updated = await updateChatProfile(chatId, { avatar_url: upload.file_url });
      setChat((prev) => (prev ? { ...prev, ...updated } : prev));
      await loadChats();
      showToast("Chat avatar updated");
    } catch {
      setError("Failed to upload chat avatar");
    } finally {
      setChatAvatarUploading(false);
    }
  }

  useEffect(() => {
    if (!open || !chatId) {
      return;
    }
    let cancelled = false;
    void (async () => {
      await refreshPanelData(chatId, true);
      if (cancelled) {
        return;
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [open, chatId]);

  useEffect(() => {
    if (!open) {
      return;
    }
    setInviteLink(null);
    setMembers([]);
    setMemberUsers({});
    setBans([]);
    setBannedUsers({});
    setSearchQuery("");
    setSearchResults([]);
    setMemberFilter("");
    setBannedFilter("");
  }, [chatId, open]);

  useEffect(() => {
    if (!open || !chatId) {
      return;
    }
    const onRealtimeChatUpdated = (event: Event) => {
      const realtimeEvent = event as CustomEvent<{ chatId?: number }>;
      const updatedChatId = Number(realtimeEvent.detail?.chatId);
      if (!Number.isFinite(updatedChatId) || updatedChatId !== chatId) {
        return;
      }
      void refreshPanelData(chatId, false);
    };
    window.addEventListener("bm:chat-updated", onRealtimeChatUpdated);
    return () => window.removeEventListener("bm:chat-updated", onRealtimeChatUpdated);
  }, [open, chatId]);

  useEffect(() => {
    if (!open || !chatId) {
      return;
    }
    const timer = window.setInterval(() => {
      void refreshPanelData(chatId, false);
    }, 15000);
    return () => window.clearInterval(timer);
  }, [open, chatId]);

  useEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        onClose();
      }
    };
    if (open) {
      window.addEventListener("keydown", onKeyDown);
    }
    return () => window.removeEventListener("keydown", onKeyDown);
  }, [open, onClose]);

  if (!open || !chatId) {
    return null;
  }

  return createPortal(
      <div
      className="fixed inset-0 z-[120] bg-slate-950/55"
      onClick={() => {
        setAttachmentCtx(null);
        setMemberCtx(null);
        setBanCtx(null);
        onClose();
      }}
    >
      <aside
        className="absolute right-0 top-0 flex h-full w-full max-w-sm flex-col border-l border-slate-700/70 bg-slate-900/95 shadow-2xl"
        onClick={(e) => {
          setAttachmentCtx(null);
          setMemberCtx(null);
          setBanCtx(null);
          e.stopPropagation();
        }}
      >
        <div className="flex items-center justify-between border-b border-slate-700/60 px-4 py-3">
          <p className="text-sm font-semibold">Chat info</p>
          <button className="rounded bg-slate-700 px-2 py-1 text-xs" onClick={onClose}>Close</button>
        </div>

        <div className="tg-scrollbar min-h-0 flex-1 overflow-y-auto px-4 py-3">
          {loading ? <p className="text-sm text-slate-300">Loading...</p> : null}
          {error ? <p className="text-sm text-red-400">{error}</p> : null}

          {chat ? (
            <>
            <div className="mb-3 rounded-lg border border-slate-700/70 bg-slate-800/60 p-4">
              <div className="flex items-center gap-3">
                {(chat.type === "private" ? (counterpartProfile?.avatar_url || chat.counterpart_avatar_url) : chat.avatar_url) ? (
                  <img
                    alt="avatar"
                    className="h-16 w-16 rounded-full object-cover"
                    src={chat.type === "private" ? (counterpartProfile?.avatar_url || chat.counterpart_avatar_url || "") : (chat.avatar_url || "")}
                  />
                ) : (
                  <div className="flex h-16 w-16 items-center justify-center rounded-full bg-sky-500/30 text-xl font-semibold uppercase text-sky-100">
                    {initialsFromName(chat.display_title || chat.title || counterpartProfile?.name || chat.type)}
                  </div>
                )}
                <div className="min-w-0">
                  <p className="truncate text-base font-semibold">{chatLabel(chat)}</p>
                  <p className="truncate text-xs text-slate-400">
                    {chat.type === "private"
                      ? privateChatStatusLabel(chat)
                      : chat.type === "group"
                        ? `${chat.members_count ?? members.length} members, ${chat.online_count ?? 0} online`
                        : `${chat.subscribers_count ?? chat.members_count ?? members.length} subscribers`}
                  </p>
                </div>
              </div>
              {chat.type === "private" && counterpartProfile?.username ? <p className="mt-3 text-xs text-sky-300">@{counterpartProfile.username}</p> : null}
              {chat.type !== "private" && chat.handle ? <p className="mt-3 text-xs text-sky-300">@{chat.handle}</p> : null}
              {chat.type === "private" && counterpartProfile?.bio ? <p className="mt-2 text-sm text-slate-300">{counterpartProfile.bio}</p> : null}
              {chat.type !== "private" && chat.description ? <p className="mt-2 text-sm text-slate-300">{chat.description}</p> : null}
            </div>

            <div className="mb-3 rounded-lg border border-slate-700/70 bg-slate-800/60 p-3">
              <div className="mb-2 flex items-center justify-between">
                <p className="text-xs text-slate-400">Notifications</p>
                <button
                  className="rounded bg-slate-700 px-2 py-1 text-xs disabled:opacity-60"
                  disabled={savingMute}
                  onClick={async () => {
                    setSavingMute(true);
                    try {
                      const updated = await updateChatNotificationSettings(chatId, !muted);
                      setMuted(updated.muted);
                      updateChatMuted(chatId, updated.muted);
                    } catch {
                      setError("Failed to update notifications");
                    } finally {
                      setSavingMute(false);
                    }
                  }}
                >
                  {muted ? "Unmute" : "Mute"}
                </button>
              </div>
              <p className="mb-2 text-xs text-slate-300">{muted ? "Chat notifications are muted." : "Chat notifications are enabled."}</p>
              {isGroupLike && canEditTitle ? (
                <>
                  <p className="mt-2 text-xs text-slate-400">Avatar</p>
                  <div className="mt-1 flex items-center gap-2">
                    <label className="cursor-pointer rounded bg-slate-700 px-2 py-1 text-xs hover:bg-slate-600">
                      {chatAvatarUploading ? "Uploading..." : "Upload avatar"}
                      <input
                        accept="image/*"
                        className="hidden"
                        disabled={chatAvatarUploading}
                        onChange={(e) => {
                          const file = e.target.files?.[0];
                          e.currentTarget.value = "";
                          if (!file) {
                            return;
                          }
                          setAvatarCropFile(file);
                        }}
                        type="file"
                      />
                    </label>
                    {chat.avatar_url ? (
                      <button
                        className="rounded bg-slate-700 px-2 py-1 text-xs hover:bg-slate-600"
                        onClick={async () => {
                          try {
                            const updated = await updateChatProfile(chatId, { avatar_url: null });
                            setChat((prev) => (prev ? { ...prev, ...updated } : prev));
                            await loadChats();
                            showToast("Chat avatar removed");
                          } catch {
                            setError("Failed to remove chat avatar");
                          }
                        }}
                        type="button"
                      >
                        Remove avatar
                      </button>
                    ) : null}
                  </div>
                  <p className="mt-2 text-xs text-slate-400">Title</p>
                  <input
                    className="mt-1 w-full rounded bg-slate-800 px-3 py-2 text-sm outline-none"
                    value={titleDraft}
                    onChange={(e) => setTitleDraft(e.target.value)}
                  />
                  <button
                    className="mt-2 w-full rounded bg-sky-500 px-3 py-2 text-sm font-semibold text-slate-950 disabled:opacity-60"
                    disabled={savingTitle || !titleDraft.trim() || chatAvatarUploading}
                    onClick={async () => {
                      setSavingTitle(true);
                      try {
                        const updated = await updateChatProfile(chatId, { title: titleDraft.trim() });
                        setChat((prev) => (prev ? { ...prev, ...updated } : prev));
                        await loadChats();
                      } catch {
                        setError("Failed to update title");
                      } finally {
                        setSavingTitle(false);
                      }
                    }}
                  >
                    Save title
                  </button>
                </>
              ) : null}
              {isGroupLike && canManageMembers ? (
                <div className="mt-2">
                  <button
                    className="w-full rounded bg-slate-700 px-3 py-2 text-xs"
                    onClick={async () => {
                      try {
                        const link = await createInviteLink(chatId);
                        setInviteLink(link.invite_url);
                        if (navigator.clipboard?.writeText) {
                          await navigator.clipboard.writeText(link.invite_url);
                          showToast("Invite link copied");
                        }
                      } catch {
                        setError("Failed to create invite link");
                      }
                    }}
                  >
                    Create invite link
                  </button>
                  {inviteLink ? (
                    <>
                      <p className="mt-1 break-all text-[11px] text-sky-300">{inviteLink}</p>
                      <div className="mt-2 flex items-center gap-2">
                        <button
                          className="rounded bg-slate-700 px-2 py-1 text-[11px] hover:bg-slate-600"
                          onClick={async () => {
                            try {
                              await navigator.clipboard.writeText(inviteLink);
                              showToast("Invite link copied");
                            } catch {
                              setError("Failed to copy invite link");
                            }
                          }}
                          type="button"
                        >
                          Copy
                        </button>
                        <button
                          className="rounded bg-slate-700 px-2 py-1 text-[11px] hover:bg-slate-600"
                          onClick={async () => {
                            try {
                              const link = await createInviteLink(chatId);
                              setInviteLink(link.invite_url);
                              showToast("Invite link regenerated");
                            } catch {
                              setError("Failed to regenerate invite link");
                            }
                          }}
                          type="button"
                        >
                          Regenerate
                        </button>
                      </div>
                    </>
                  ) : null}
                </div>
              ) : null}
            </div>

            {showMembersSection ? (
              <div className="mb-3 rounded-lg border border-slate-700/70 bg-slate-800/60 p-3">
                <p className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-300">Members ({members.length})</p>
                <input
                  className="mb-2 w-full rounded bg-slate-800 px-2 py-1.5 text-xs outline-none"
                  onChange={(e) => setMemberFilter(e.target.value)}
                  placeholder="Search members"
                  value={memberFilter}
                />
                <div className="tg-scrollbar max-h-56 space-y-2 overflow-auto">
                  {filteredMembers.map((member) => {
                    const user = memberUsers[member.user_id];
                    const isSelf = member.user_id === me?.id;
                    const canOpenMemberMenu =
                      canManageMembers &&
                      !isSelf &&
                      (myRoleNormalized === "owner" || (myRoleNormalized === "admin" && member.role === "member"));
                    return (
                      <button
                        className="block w-full rounded border border-slate-700/60 bg-slate-900/60 p-2 text-left hover:bg-slate-800/70"
                        key={member.id}
                        onContextMenu={(event) => {
                          if (!canOpenMemberMenu) {
                            return;
                          }
                          event.preventDefault();
                          setMemberCtx({
                            x: Math.min(event.clientX + 4, window.innerWidth - 210),
                            y: Math.min(event.clientY + 4, window.innerHeight - 170),
                            member,
                          });
                        }}
                        type="button"
                      >
                        <div className="flex items-start justify-between gap-2">
                          <div className="flex min-w-0 items-start gap-2">
                            {user?.avatar_url ? (
                              <img alt="avatar" className="h-8 w-8 shrink-0 rounded-full object-cover" src={user.avatar_url} />
                            ) : (
                              <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-sky-500/30 text-[11px] font-semibold uppercase text-sky-100">
                                {initialsFromName(user?.name || user?.username || `user ${member.user_id}`)}
                              </div>
                            )}
                            <div className="min-w-0 flex-1">
                              <p className="flex items-center gap-1 truncate text-sm font-semibold">
                                <span className="truncate">{user?.name || `user #${member.user_id}`}</span>
                                {member.role === "owner" ? <span className="rounded bg-amber-500/20 px-1.5 py-0.5 text-[10px] text-amber-300">👑 owner</span> : null}
                                {member.role === "admin" ? <span className="rounded bg-sky-500/20 px-1.5 py-0.5 text-[10px] text-sky-300">👑 admin</span> : null}
                              </p>
                              <p className="truncate text-xs text-slate-400">@{user?.username || "unknown"}</p>
                              {canOpenMemberMenu ? <p className="mt-1 text-[11px] text-slate-500">Right click for actions</p> : null}
                            </div>
                          </div>
                          {canOpenMemberMenu ? (
                            <button
                              className="rounded bg-slate-700 px-1.5 py-0.5 text-xs hover:bg-slate-600"
                              onClick={(event) => {
                                event.stopPropagation();
                                const rect = event.currentTarget.getBoundingClientRect();
                                setMemberCtx({
                                  x: Math.min(rect.left, window.innerWidth - 210),
                                  y: Math.min(rect.bottom + 4, window.innerHeight - 170),
                                  member,
                                });
                              }}
                              type="button"
                            >
                              ⋯
                            </button>
                          ) : null}
                        </div>
                      </button>
                    );
                  })}
                </div>
              </div>
            ) : (
              <div className="mb-3 rounded-lg border border-slate-700/70 bg-slate-800/60 p-3">
                {chat.is_saved ? (
                  <p className="text-sm text-slate-300">Saved Messages is your personal cloud chat.</p>
                ) : chat.type === "private" ? (
                  <p className="text-sm text-slate-300">
                    {chat.counterpart_is_online
                      ? "User is online"
                      : chat.counterpart_last_seen_at
                        ? `Last seen ${formatLastSeen(chat.counterpart_last_seen_at)}`
                        : "Last seen recently"}
                  </p>
                ) : (
                  <p className="text-sm text-slate-300">No extra information.</p>
                )}
              </div>
            )}

            {showMembersSection && canManageMembers ? (
              <div className="mb-3 rounded-lg border border-slate-700/70 bg-slate-800/60 p-3">
                <p className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-300">Add member</p>
                <input
                  className="mb-2 w-full rounded bg-slate-800 px-3 py-2 text-sm outline-none"
                  placeholder="@username"
                  value={searchQuery}
                  onChange={async (e) => {
                    const value = e.target.value;
                    setSearchQuery(value);
                    if (value.trim().replace("@", "").length < 2) {
                      setSearchResults([]);
                      return;
                    }
                    try {
                      const users = await searchUsers(value);
                      setSearchResults(
                        users.filter(
                          (u) =>
                            !members.some((m) => m.user_id === u.id) &&
                            !bans.some((ban) => ban.user_id === u.id)
                        )
                      );
                    } catch {
                      setError("Failed to search users");
                    }
                  }}
                />
                <div className="tg-scrollbar max-h-40 space-y-1 overflow-auto">
                  {searchResults.map((user) => (
                    <button
                      className="block w-full rounded bg-slate-900/70 px-3 py-2 text-left text-sm hover:bg-slate-700"
                      key={user.id}
                      onClick={async () => {
                        try {
                          await addChatMember(chatId, user.id);
                          setSearchQuery("");
                          setSearchResults([]);
                          await refreshPanelData(chatId, false);
                        } catch {
                          setError("Failed to add member");
                        }
                      }}
                    >
                      <div className="flex items-start gap-2">
                        {user.avatar_url ? (
                          <img alt="avatar" className="h-8 w-8 shrink-0 rounded-full object-cover" src={user.avatar_url} />
                        ) : (
                          <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-slate-700 text-[11px] font-semibold uppercase text-slate-200">
                            {initialsFromName(user.name || user.username || `user ${user.id}`)}
                          </div>
                        )}
                        <div className="min-w-0">
                          <p className="truncate font-semibold">{user.name}</p>
                          <p className="truncate text-xs text-slate-400">@{user.username}</p>
                        </div>
                      </div>
                    </button>
                  ))}
                  {searchQuery.trim().replace("@", "").length >= 2 && searchResults.length === 0 ? (
                    <p className="px-1 py-1 text-xs text-slate-400">
                      No users available to add (already in chat or banned).
                    </p>
                  ) : null}
                </div>
              </div>
            ) : null}
            {showMembersSection && canManageMembers ? (
              <div className="mb-3 rounded-lg border border-slate-700/70 bg-slate-800/60 p-3">
                <p className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-300">Banned users ({bans.length})</p>
                <input
                  className="mb-2 w-full rounded bg-slate-800 px-2 py-1.5 text-xs outline-none"
                  onChange={(e) => setBannedFilter(e.target.value)}
                  placeholder="Search banned users"
                  value={bannedFilter}
                />
                {bans.length === 0 ? <p className="text-xs text-slate-400">No banned users</p> : null}
                <div className="tg-scrollbar max-h-44 space-y-1 overflow-auto">
                  {filteredBans.map((ban) => {
                    const user = bannedUsers[ban.user_id];
                    return (
                      <div
                        className="flex w-full items-center justify-between rounded border border-slate-700/60 bg-slate-900/60 px-2 py-1.5 text-left hover:bg-slate-800/70"
                        key={`ban-${ban.user_id}`}
                        onContextMenu={(event) => {
                          event.preventDefault();
                          setBanCtx({
                            x: Math.min(event.clientX + 4, window.innerWidth - 210),
                            y: Math.min(event.clientY + 4, window.innerHeight - 130),
                            ban,
                          });
                        }}
                      >
                        <div className="flex min-w-0 items-start gap-2">
                          {user?.avatar_url ? (
                            <img alt="avatar" className="h-8 w-8 shrink-0 rounded-full object-cover" src={user.avatar_url} />
                          ) : (
                            <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-slate-700 text-[11px] font-semibold uppercase text-slate-200">
                              {initialsFromName(user?.name || user?.username || `user ${ban.user_id}`)}
                            </div>
                          )}
                          <div className="min-w-0">
                            <p className="truncate text-xs font-semibold text-slate-200">{user?.name || `user #${ban.user_id}`}</p>
                            <p className="truncate text-[11px] text-slate-400">@{user?.username || "unknown"}</p>
                            <p className="truncate text-[10px] text-slate-500">
                              by @{bannedUsers[ban.banned_by_user_id]?.username || `user${ban.banned_by_user_id}`} • {new Date(ban.created_at).toLocaleString()}
                            </p>
                            <p className="truncate text-[10px] text-slate-500">Right click for actions</p>
                          </div>
                        </div>
                        <button
                          className="rounded bg-slate-700 px-2 py-1 text-[11px] hover:bg-slate-600"
                          onClick={async (event) => {
                            event.stopPropagation();
                            try {
                              await unbanChatMember(chatId, ban.user_id);
                              await refreshPanelData(chatId, false);
                              showToast("User unbanned");
                            } catch {
                              setError("Failed to unban user");
                            }
                          }}
                          type="button"
                        >
                          Unban
                        </button>
                      </div>
                    );
                  })}
                  {bans.length > 0 && filteredBans.length === 0 ? (
                    <p className="px-1 py-1 text-xs text-slate-400">No banned users match this filter.</p>
                  ) : null}
                </div>
              </div>
            ) : null}

            <div className="mb-3 rounded-lg border border-slate-700/70 bg-slate-800/60 p-3">
              <p className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-300">
                Media & Files {attachments.length > 0 ? `(${attachments.length})` : ""}
              </p>
              <div className="sticky top-0 z-10 mb-2 flex items-center gap-2 border-b border-slate-700/60 bg-slate-800/90 pb-2 pt-1 text-xs backdrop-blur">
                <button
                  className={`rounded px-2 py-1 ${attachmentsTab === "all" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
                  onClick={() => setAttachmentsTab("all")}
                  type="button"
                >
                  All ({attachments.length})
                </button>
                <button
                  className={`rounded px-2 py-1 ${attachmentsTab === "photos" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
                  onClick={() => setAttachmentsTab("photos")}
                  type="button"
                >
                  Photos ({photoAttachments.length})
                </button>
                <button
                  className={`rounded px-2 py-1 ${attachmentsTab === "videos" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
                  onClick={() => setAttachmentsTab("videos")}
                  type="button"
                >
                  Videos ({videoAttachments.length})
                </button>
                <button
                  className={`rounded px-2 py-1 ${attachmentsTab === "audio" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
                  onClick={() => setAttachmentsTab("audio")}
                  type="button"
                >
                  Audio ({audioAttachments.length})
                </button>
                <button
                  className={`rounded px-2 py-1 ${attachmentsTab === "voice" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
                  onClick={() => setAttachmentsTab("voice")}
                  type="button"
                >
                  Voice ({voiceAttachments.length})
                </button>
                <button
                  className={`rounded px-2 py-1 ${attachmentsTab === "links" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
                  onClick={() => setAttachmentsTab("links")}
                  type="button"
                >
                  Links ({linkItems.length})
                </button>
              </div>
              {attachmentsLoading ? <p className="text-xs text-slate-400">Loading attachments...</p> : null}
              {!attachmentsLoading && attachments.length === 0 ? <p className="text-xs text-slate-400">No attachments yet</p> : null}

              {!attachmentsLoading && (attachmentsTab === "all" || attachmentsTab === "photos" || attachmentsTab === "videos") ? (
                <>
                  <div className="grid grid-cols-3 gap-1">
                    {(attachmentsTab === "photos" ? photoAttachments : attachmentsTab === "videos" ? videoAttachments : [...photoAttachments, ...videoAttachments])
                      .slice(0, 120)
                      .map((item) => (
                      <button
                        className="group relative aspect-square overflow-hidden rounded-md border border-slate-700/70 bg-slate-900"
                        key={`media-item-${item.id}`}
                        onClick={() => {
                          const mediaItems = [...photoAttachments, ...videoAttachments]
                            .sort((a, b) => b.id - a.id)
                            .map((it) => ({ url: it.file_url, type: it.file_type.startsWith("video/") ? "video" as const : "image" as const, messageId: it.message_id }));
                          const idx = mediaItems.findIndex((it) => it.url === item.file_url && it.messageId === item.message_id);
                          setMediaViewer({ items: mediaItems, index: idx >= 0 ? idx : 0 });
                        }}
                        onContextMenu={(event) => {
                          event.preventDefault();
                          setAttachmentCtx({
                            x: Math.min(event.clientX + 4, window.innerWidth - 190),
                            y: Math.min(event.clientY + 4, window.innerHeight - 120),
                            url: item.file_url,
                            messageId: item.message_id,
                          });
                        }}
                        type="button"
                      >
                        {item.file_type.startsWith("video/") ? (
                          <video className="h-full w-full object-cover transition group-hover:scale-105" muted src={item.file_url} />
                        ) : (
                          <img alt="attachment" className="h-full w-full object-cover transition group-hover:scale-105" src={item.file_url} />
                        )}
                      </button>
                    ))}
                  </div>
                </>
              ) : null}
              {!attachmentsLoading && (attachmentsTab === "all" || attachmentsTab === "audio" || attachmentsTab === "voice") ? (
                <div className="tg-scrollbar max-h-72 space-y-1 overflow-auto">
                  {(attachmentsTab === "audio" ? audioAttachments : attachmentsTab === "voice" ? voiceAttachments : allAttachmentItems)
                    .filter((item) => !item.file_type.startsWith("image/") && !item.file_type.startsWith("video/"))
                    .slice(0, 120)
                    .map((item) => (
                    <button
                      className="block w-full rounded-md border border-slate-700/60 bg-slate-900/70 px-2 py-1.5 text-left hover:bg-slate-700/70"
                      key={`file-item-${item.id}`}
                      onClick={() => jumpToMessage(item.message_id)}
                      onContextMenu={(event) => {
                        event.preventDefault();
                        setAttachmentCtx({
                          x: Math.min(event.clientX + 4, window.innerWidth - 190),
                          y: Math.min(event.clientY + 4, window.innerHeight - 120),
                          url: item.file_url,
                          messageId: item.message_id,
                        });
                      }}
                      type="button"
                    >
                      <p className="truncate text-xs font-semibold text-slate-200">{extractFileName(item.file_url)}</p>
                      <p className="text-[11px] text-slate-400">
                        {attachmentKind(item.file_type)} • {formatBytes(item.file_size)}
                      </p>
                    </button>
                  ))}
                </div>
              ) : null}
              {!attachmentsLoading && attachmentsTab === "links" ? (
                <div className="tg-scrollbar max-h-72 space-y-1 overflow-auto">
                  {linkItems.length === 0 ? <p className="text-xs text-slate-400">No links found</p> : null}
                  {linkItems.map((item, index) => (
                    <button
                      className="block w-full rounded-md border border-slate-700/60 bg-slate-900/70 px-2 py-1.5 text-left hover:bg-slate-700/70"
                      key={`link-item-${item.messageId}-${index}`}
                      onClick={() => jumpToMessage(item.messageId)}
                      onContextMenu={(event) => {
                        event.preventDefault();
                        setAttachmentCtx({
                          x: Math.min(event.clientX + 4, window.innerWidth - 190),
                          y: Math.min(event.clientY + 4, window.innerHeight - 120),
                          url: item.url,
                          messageId: item.messageId,
                        });
                      }}
                      type="button"
                    >
                      <p className="truncate text-xs font-semibold text-sky-300">{shortLink(item.url)}</p>
                      <p className="truncate text-[11px] text-slate-400">{item.url}</p>
                      <p className="text-[11px] text-slate-500">{new Date(item.createdAt).toLocaleString()}</p>
                    </button>
                  ))}
                </div>
              ) : null}
            </div>

            {chat.type === "private" && !chat.is_saved && chat.counterpart_user_id ? (
              <button
                className="mb-3 w-full rounded bg-slate-700 px-3 py-2 text-sm disabled:opacity-60"
                disabled={savingBlock}
                onClick={async () => {
                  setSavingBlock(true);
                  try {
                    if (counterpartBlocked) {
                      await unblockUser(chat.counterpart_user_id!);
                      setCounterpartBlocked(false);
                    } else {
                      await blockUser(chat.counterpart_user_id!);
                      setCounterpartBlocked(true);
                    }
                  } catch {
                    setError("Failed to update block status");
                  } finally {
                    setSavingBlock(false);
                  }
                }}
              >
                {counterpartBlocked ? "Unblock user" : "Block user"}
              </button>
            ) : null}

            {showMembersSection && (chat.type === "group" || chat.type === "channel") ? (
              <>
                {chat.type === "group" && (myRoleNormalized === "owner" || myRoleNormalized === "admin") ? (
                  <button
                    className="mb-2 w-full rounded bg-red-500 px-3 py-2 text-sm font-semibold text-white hover:bg-red-400"
                    onClick={async () => {
                      const confirmed = window.confirm("Delete this group for all members?");
                      if (!confirmed) {
                        return;
                      }
                      try {
                        await deleteChat(chatId, true);
                        await loadChats();
                        setActiveChatId(null);
                        onClose();
                      } catch {
                        setError("Failed to delete group");
                      }
                    }}
                    type="button"
                  >
                    Delete group for all
                  </button>
                ) : null}
                {chat.type === "channel" && (myRoleNormalized === "owner" || myRoleNormalized === "admin") ? (
                  <button
                    className="mb-2 w-full rounded bg-red-500 px-3 py-2 text-sm font-semibold text-white hover:bg-red-400"
                    onClick={async () => {
                      const confirmed = window.confirm("Delete this channel for all subscribers?");
                      if (!confirmed) {
                        return;
                      }
                      try {
                        await deleteChat(chatId, true);
                        await loadChats();
                        setActiveChatId(null);
                        onClose();
                      } catch {
                        setError("Failed to delete channel");
                      }
                    }}
                    type="button"
                  >
                    Delete channel for all
                  </button>
                ) : null}
                <button
                  className="w-full rounded bg-slate-700 px-3 py-2 text-sm disabled:cursor-not-allowed disabled:opacity-60"
                  disabled={!canLeaveGroupLikeChat}
                  onClick={async () => {
                    if (!canLeaveGroupLikeChat) {
                      return;
                    }
                    try {
                      await leaveChat(chatId);
                      await loadChats();
                      setActiveChatId(null);
                      onClose();
                    } catch {
                      setError("Failed to leave chat");
                    }
                  }}
                >
                  {canLeaveGroupLikeChat
                    ? (chat.type === "channel" ? "Leave channel" : "Leave chat")
                    : "Owner cannot leave while members remain"}
                </button>
              </>
            ) : null}
            </>
          ) : null}
        </div>
      </aside>
      {attachmentCtx ? (
        <div
          className="fixed z-[130] w-44 rounded-lg border border-slate-700/90 bg-slate-900/95 p-1 shadow-2xl"
          style={{ left: attachmentCtx.x, top: attachmentCtx.y }}
          onClick={(event) => event.stopPropagation()}
        >
          <button
            className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
            onClick={() => {
              window.open(attachmentCtx.url, "_blank", "noopener,noreferrer");
              setAttachmentCtx(null);
            }}
            type="button"
          >
            Open
          </button>
          <button
            className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
            onClick={async () => {
              try {
                await downloadFileFromUrl(attachmentCtx.url);
                showToast("File downloaded");
              } catch {
                showToast("Download failed");
              } finally {
                setAttachmentCtx(null);
              }
            }}
            type="button"
          >
            Download
          </button>
          <button
            className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
            onClick={async () => {
              try {
                await navigator.clipboard.writeText(attachmentCtx.url);
                showToast("Link copied");
              } catch {
                showToast("Copy failed");
                return;
              } finally {
                setAttachmentCtx(null);
              }
            }}
            type="button"
          >
            Copy link
          </button>
          <button
            className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
            onClick={() => {
              jumpToMessage(attachmentCtx.messageId);
              setAttachmentCtx(null);
            }}
            type="button"
          >
            Jump to message
          </button>
        </div>
      ) : null}
      {memberCtx ? (
        <div
          className="fixed z-[130] w-52 rounded-lg border border-slate-700/90 bg-slate-900/95 p-1 shadow-2xl"
          style={{ left: memberCtx.x, top: memberCtx.y }}
          onClick={(event) => event.stopPropagation()}
        >
          {myRoleNormalized === "owner" && memberCtx.member.role === "member" ? (
            <button
              className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
              onClick={async () => {
                try {
                  await updateChatMemberRole(chatId, memberCtx.member.user_id, "admin");
                  await refreshPanelData(chatId, false);
                } catch {
                  setError("Failed to update role");
                } finally {
                  setMemberCtx(null);
                }
              }}
              type="button"
            >
              Make admin
            </button>
          ) : null}
          {myRoleNormalized === "owner" && memberCtx.member.role === "admin" ? (
            <button
              className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
              onClick={async () => {
                try {
                  await updateChatMemberRole(chatId, memberCtx.member.user_id, "member");
                  await refreshPanelData(chatId, false);
                } catch {
                  setError("Failed to update role");
                } finally {
                  setMemberCtx(null);
                }
              }}
              type="button"
            >
              Remove admin rights
            </button>
          ) : null}
          {myRoleNormalized === "owner" && memberCtx.member.role !== "owner" ? (
            <button
              className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
              onClick={async () => {
                try {
                  await updateChatMemberRole(chatId, memberCtx.member.user_id, "owner");
                  await refreshPanelData(chatId, false);
                } catch {
                  setError("Failed to transfer ownership");
                } finally {
                  setMemberCtx(null);
                }
              }}
              type="button"
            >
              Transfer ownership
            </button>
          ) : null}
          {(myRoleNormalized === "owner" || (myRoleNormalized === "admin" && memberCtx.member.role === "member")) ? (
            <button
              className="block w-full rounded px-2 py-1.5 text-left text-sm text-red-300 hover:bg-slate-800"
              onClick={async () => {
                try {
                  await banChatMember(chatId, memberCtx.member.user_id);
                  await refreshPanelData(chatId, false);
                } catch {
                  setError("Failed to ban member");
                } finally {
                  setMemberCtx(null);
                }
              }}
              type="button"
            >
              Ban from chat
            </button>
          ) : null}
          {(myRoleNormalized === "owner" || (myRoleNormalized === "admin" && memberCtx.member.role === "member")) ? (
            <button
              className="block w-full rounded px-2 py-1.5 text-left text-sm text-red-300 hover:bg-slate-800"
              onClick={async () => {
                try {
                  await removeChatMember(chatId, memberCtx.member.user_id);
                  await refreshPanelData(chatId, false);
                } catch {
                  setError("Failed to remove member");
                } finally {
                  setMemberCtx(null);
                }
              }}
              type="button"
            >
              Remove from chat (without ban)
            </button>
          ) : null}
          <button
            className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
            onClick={() => setMemberCtx(null)}
            type="button"
          >
            Cancel
          </button>
        </div>
      ) : null}
      {banCtx ? (
        <div
          className="fixed z-[130] w-52 rounded-lg border border-slate-700/90 bg-slate-900/95 p-1 shadow-2xl"
          style={{ left: banCtx.x, top: banCtx.y }}
          onClick={(event) => event.stopPropagation()}
        >
          <button
            className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
            onClick={async () => {
              try {
                await unbanChatMember(chatId, banCtx.ban.user_id);
                await refreshPanelData(chatId, false);
                showToast("User unbanned");
              } catch {
                setError("Failed to unban user");
              } finally {
                setBanCtx(null);
              }
            }}
            type="button"
          >
            Unban user
          </button>
          <button
            className="block w-full rounded px-2 py-1.5 text-left text-sm hover:bg-slate-800"
            onClick={() => setBanCtx(null)}
            type="button"
          >
            Cancel
          </button>
        </div>
      ) : null}
      {mediaViewer ? (
        <MediaViewer
          index={mediaViewer.index}
          items={mediaViewer.items}
          onClose={() => setMediaViewer(null)}
          onIndexChange={(nextIndex) => setMediaViewer((prev) => (prev ? { ...prev, index: nextIndex } : prev))}
          onJumpToMessage={(messageId) => jumpToMessage(messageId)}
          onToast={showToast}
          open
        />
      ) : null}
      <AvatarCropModal
        file={avatarCropFile}
        onApply={(processedFile) => {
          setAvatarCropFile(null);
          void uploadChatAvatar(processedFile);
        }}
        onCancel={() => setAvatarCropFile(null)}
        open={Boolean(avatarCropFile)}
      />
    </div>,
    document.body
  );
}

function formatLastSeen(value: string): string {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    return "recently";
  }
  const now = new Date();
  const diffMs = now.getTime() - date.getTime();
  if (diffMs < 0) {
    return "recently";
  }
  const minute = 60 * 1000;
  const hour = 60 * minute;
  const day = 24 * hour;
  if (diffMs < 2 * minute) {
    return "just now";
  }
  if (diffMs < day) {
    return `today at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
  }
  if (diffMs < 2 * day) {
    return `yesterday at ${date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
  }
  if (diffMs < 7 * day) {
    return "recently";
  }
  return date.toLocaleString(undefined, {
    day: "2-digit",
    month: "short",
    hour: "2-digit",
    minute: "2-digit"
  });
}

function chatLabel(chat: { display_title?: string | null; title?: string | null; type: "private" | "group" | "channel"; is_saved?: boolean }): string {
  if (chat.display_title?.trim()) return chat.display_title;
  if (chat.title?.trim()) return chat.title;
  if (chat.is_saved) return "Saved Messages";
  if (chat.type === "private") return "Direct chat";
  if (chat.type === "group") return "Group";
  return "Channel";
}

function privateChatStatusLabel(chat: { counterpart_is_online?: boolean | null; counterpart_last_seen_at?: string | null }): string {
  if (chat.counterpart_is_online) {
    return "online";
  }
  if (chat.counterpart_last_seen_at) {
    return `last seen ${formatLastSeen(chat.counterpart_last_seen_at)}`;
  }
  return "last seen recently";
}

function initialsFromName(value: string): string {
  const prepared = value.trim();
  if (!prepared) {
    return "?";
  }
  const parts = prepared.split(/\s+/).filter(Boolean);
  if (parts.length >= 2) {
    return `${parts[0][0] ?? ""}${parts[1][0] ?? ""}`.toUpperCase();
  }
  return (parts[0]?.slice(0, 2) ?? "?").toUpperCase();
}

function formatBytes(size: number): string {
  if (size < 1024) return `${size} B`;
  if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
  return `${(size / (1024 * 1024)).toFixed(1)} MB`;
}

function extractFileName(url: string): string {
  try {
    const parsed = new URL(url);
    const value = parsed.pathname.split("/").pop();
    return decodeURIComponent(value || "file");
  } catch {
    const value = url.split("/").pop();
    return value ? decodeURIComponent(value) : "file";
  }
}

function attachmentKind(fileType: string): string {
  if (fileType.startsWith("image/")) return "Photo";
  if (fileType.startsWith("video/")) return "Video";
  if (fileType.startsWith("audio/")) return "Audio";
  if (fileType === "application/pdf") return "PDF";
  return "File";
}

async function getRecentMessagesForLinks(chatId: number): Promise<Message[]> {
  const pages = 4;
  const all: Message[] = [];
  let beforeId: number | undefined;
  for (let i = 0; i < pages; i += 1) {
    const batch = await getMessages(chatId, beforeId);
    if (!batch.length) {
      break;
    }
    all.push(...batch);
    beforeId = batch[batch.length - 1]?.id;
    if (!beforeId || batch.length < 50) {
      break;
    }
  }
  return all;
}

function extractLinkItems(messages: Message[]): Array<{ url: string; messageId: number; createdAt: string }> {
  const out: Array<{ url: string; messageId: number; createdAt: string }> = [];
  const seen = new Set<string>();
  const regex = /\bhttps?:\/\/[^\s<>"')]+/gi;
  for (const message of messages) {
    if (message.type !== "text" || !message.text) {
      continue;
    }
    const links = message.text.match(regex) ?? [];
    for (const raw of links) {
      const url = raw.trim();
      const key = `${message.id}:${url}`;
      if (seen.has(key)) {
        continue;
      }
      seen.add(key);
      out.push({ url, messageId: message.id, createdAt: message.created_at });
    }
  }
  out.sort((a, b) => b.messageId - a.messageId);
  return out;
}

function shortLink(url: string): string {
  try {
    const parsed = new URL(url);
    return `${parsed.hostname}${parsed.pathname === "/" ? "" : parsed.pathname}`;
  } catch {
    return url;
  }
}

async function downloadFileFromUrl(url: string): Promise<void> {
  const response = await fetch(url, { mode: "cors" });
  if (!response.ok) {
    throw new Error("Download failed");
  }
  const blob = await response.blob();
  const filename = extractFileName(url);
  const blobUrl = window.URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = blobUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  link.remove();
  window.URL.revokeObjectURL(blobUrl);
}
