"use client"; import { create } from "zustand"; import { CreateRoomOptions, MatchmakingOptions, getService } from "./online/service"; import { pushNotification } from "./notification-store"; import { ChatMessage, Friend, FriendRequest, LeaderboardEntry, MatchmakingState, Room, } from "./online/types"; interface OnlineStore { friends: Friend[]; requests: FriendRequest[]; room: Room | null; matchmaking: MatchmakingState; leaderboard: LeaderboardEntry[]; loadFriends: () => Promise; addFriend: (q: string) => Promise<{ ok: boolean; messageFa: string; messageEn: string }>; acceptRequest: (id: string) => Promise; declineRequest: (id: string) => Promise; removeFriend: (id: string) => Promise; createRoom: (opts: CreateRoomOptions) => Promise; setPartner: (friendId: string | null) => Promise; inviteToSeat: (seat: 1 | 3, friendId: string) => Promise; addBot: (seat: 1 | 2 | 3) => Promise; clearSeat: (seat: 1 | 2 | 3) => Promise; startRoom: () => Promise; leaveRoom: () => Promise; acceptInvite: () => Promise; declineInvite: () => Promise; startMatchmaking: (opts: MatchmakingOptions) => Promise; cancelMatchmaking: () => Promise; loadLeaderboard: () => Promise; // chat activeChatFriend: Friend | null; chatMessages: ChatMessage[]; unread: number; // total unread messages across conversations (for nav badge) openChat: (friend: Friend) => Promise; sendChat: (text: string) => Promise; closeChat: () => void; refreshUnread: () => Promise; } let roomUnsub: (() => void) | null = null; let mmUnsub: (() => void) | null = null; let friendUnsub: (() => void) | null = null; let chatUnsub: (() => void) | null = null; const seenRequests = new Set(); export const useOnlineStore = create((set, get) => ({ friends: [], requests: [], room: null, matchmaking: { phase: "idle", players: [], elapsedMs: 0, ranked: true, stake: 0 }, leaderboard: [], loadFriends: async () => { const svc = getService(); const [friends, requests] = await Promise.all([svc.listFriends(), svc.listRequests()]); set({ friends, requests }); for (const r of requests) { if (seenRequests.has(r.id)) continue; seenRequests.add(r.id); pushNotification({ kind: "friend_request", titleFa: "درخواست دوستی جدید", titleEn: "New friend request", bodyFa: r.from.displayName, bodyEn: r.from.displayName, icon: "👥", }); } if (!friendUnsub) friendUnsub = svc.onFriends((f) => set({ friends: f })); }, addFriend: async (q) => { const res = await getService().addFriend(q); if (res.ok) await get().loadFriends(); return res; }, acceptRequest: async (id) => { await getService().acceptRequest(id); const requests = await getService().listRequests(); set({ requests }); }, declineRequest: async (id) => { await getService().declineRequest(id); set({ requests: get().requests.filter((r) => r.id !== id) }); }, removeFriend: async (id) => { await getService().removeFriend(id); }, createRoom: async (opts) => { const svc = getService(); const room = await svc.createRoom(opts); set({ room }); if (roomUnsub) roomUnsub(); roomUnsub = svc.onRoom((r) => set({ room: { ...r } })); }, setPartner: async (friendId) => { const r = await getService().setPartner(get().room!.id, friendId); set({ room: { ...r } }); }, inviteToSeat: async (seat, friendId) => { const r = await getService().inviteToSeat(get().room!.id, seat, friendId); set({ room: { ...r } }); }, addBot: async (seat) => { const r = await getService().addBot(get().room!.id, seat); set({ room: { ...r } }); }, clearSeat: async (seat) => { const r = await getService().clearSeat(get().room!.id, seat); set({ room: { ...r } }); }, startRoom: async () => { const r = await getService().startRoom(get().room!.id); set({ room: { ...r } }); }, leaveRoom: async () => { if (get().room) await getService().leaveRoom(get().room!.id); if (roomUnsub) { roomUnsub(); roomUnsub = null; } set({ room: null }); }, acceptInvite: async () => { const svc = getService(); if (roomUnsub) roomUnsub(); roomUnsub = svc.onRoom((r) => set({ room: { ...r } })); // subscribe first so we catch the room push await svc.acceptInvite(); }, declineInvite: async () => { await getService().declineInvite(); }, startMatchmaking: async (opts) => { const svc = getService(); if (mmUnsub) mmUnsub(); mmUnsub = svc.onMatchmaking((s) => set({ matchmaking: s })); await svc.startMatchmaking(opts); }, cancelMatchmaking: async () => { await getService().cancelMatchmaking(); if (mmUnsub) { mmUnsub(); mmUnsub = null; } set({ matchmaking: { phase: "idle", players: [], elapsedMs: 0, ranked: true, stake: 0 } }); }, loadLeaderboard: async () => { const leaderboard = await getService().getLeaderboard(); set({ leaderboard }); }, activeChatFriend: null, chatMessages: [], unread: 0, refreshUnread: async () => { try { const convs = await getService().listConversations(); set({ unread: convs.reduce((n, c) => n + (c.unread ?? 0), 0) }); } catch { /* ignore */ } }, openChat: async (friend) => { const svc = getService(); set({ activeChatFriend: friend, chatMessages: await svc.getMessages(friend.id) }); await svc.markRead(friend.id); get().refreshUnread(); if (chatUnsub) chatUnsub(); chatUnsub = svc.onChat((friendId, msgs) => { const active = get().activeChatFriend; if (active && active.id === friendId) { set({ chatMessages: msgs }); svc.markRead(friendId); } }); }, sendChat: async (text) => { const friend = get().activeChatFriend; if (!friend || !text.trim()) return; await getService().sendMessage(friend.id, text); set({ chatMessages: await getService().getMessages(friend.id) }); }, closeChat: () => { if (chatUnsub) { chatUnsub(); chatUnsub = null; } set({ activeChatFriend: null, chatMessages: [] }); get().refreshUnread(); }, }));