import { useQueryClient } from "@tanstack/react-query"; import { useServerMutation, useServerSuspenseQuery } from "@/lib/tanstack-query/hooks"; import { Match } from "@/features/matches/types"; import { getMatchesBetweenTeams, getMatchesBetweenPlayers, reportMatchScore, confirmMatchScore, clearMatchReport, } from "./server"; export const matchKeys = { headToHeadTeams: (team1Id: string, team2Id: string) => ['matches', 'headToHead', 'teams', team1Id, team2Id] as const, headToHeadPlayers: (player1Id: string, player2Id: string) => ['matches', 'headToHead', 'players', player1Id, player2Id] as const, }; export const matchQueries = { headToHeadTeams: (team1Id: string, team2Id: string) => ({ queryKey: matchKeys.headToHeadTeams(team1Id, team2Id), queryFn: () => getMatchesBetweenTeams({ data: { team1Id, team2Id } }), }), headToHeadPlayers: (player1Id: string, player2Id: string) => ({ queryKey: matchKeys.headToHeadPlayers(player1Id, player2Id), queryFn: () => getMatchesBetweenPlayers({ data: { player1Id, player2Id } }), }), }; export const useTeamHeadToHead = (team1Id: string, team2Id: string, enabled = true) => useServerSuspenseQuery({ ...matchQueries.headToHeadTeams(team1Id, team2Id), enabled, }); export const usePlayerHeadToHead = (player1Id: string, player2Id: string, enabled = true) => useServerSuspenseQuery({ ...matchQueries.headToHeadPlayers(player1Id, player2Id), enabled, }); const tournamentsRoot = { queryKey: ["tournaments"] as const }; const reportTeamId = (t: Match["home"]): string | undefined => !t ? undefined : typeof t === "string" ? t : t.id; const reportTeamPlayerIds = (t: Match["home"]): string[] => t && typeof t !== "string" ? (t.players ?? []).map((p) => p.id) : []; function patchTournamentsData( data: unknown, matchId: string, patch: (match: Match) => Partial ): unknown { if (!data) return data; if (Array.isArray(data)) { return data.map((entry) => patchTournamentsData(entry, matchId, patch)); } const tournament = data as { matches?: Match[] }; if (!Array.isArray(tournament.matches)) return data; let changed = false; const matches = tournament.matches.map((m) => { if (m.id !== matchId) return m; changed = true; return { ...m, ...patch(m) }; }); return changed ? { ...tournament, matches } : data; } type MatchVariables = { data: { matchId: string } }; type TournamentsSnapshot = [readonly unknown[], unknown][]; function useOptimisticMatchMutation( options: Parameters>[0] & { buildPatch: (match: Match, variables: TVariables) => Partial; } ) { const queryClient = useQueryClient(); const { buildPatch, ...mutationOptions } = options; return useServerMutation({ ...mutationOptions, onMutate: async (variables) => { const { matchId } = variables.data; await queryClient.cancelQueries(tournamentsRoot); const previous = queryClient.getQueriesData(tournamentsRoot); queryClient.setQueriesData(tournamentsRoot, (data: unknown) => patchTournamentsData(data, matchId, (match) => buildPatch(match, variables)) ); return { previous }; }, onError: (error, variables, onMutateResult, context) => { if (context && typeof context === "object" && "previous" in context) { const snapshot = (context as { previous: TournamentsSnapshot }).previous; for (const [key, data] of snapshot) { queryClient.setQueryData(key, data); } } mutationOptions.onError?.(error, variables, onMutateResult, context); }, onSettled: (data, error, variables, onMutateResult, context) => { queryClient.invalidateQueries(tournamentsRoot); mutationOptions.onSettled?.(data, error, variables, onMutateResult, context); }, }); } type MatchMutationOptions = { onSuccess?: Parameters[0]["onSuccess"]; }; export const useReportMatchScore = (userId?: string, options?: MatchMutationOptions) => useOptimisticMatchMutation({ mutationFn: reportMatchScore, showSuccessToast: false, ...options, buildPatch: (match, variables) => { const onHome = !!userId && reportTeamPlayerIds(match.home).includes(userId); const onAway = !!userId && reportTeamPlayerIds(match.away).includes(userId); const callerTeamId = onHome ? reportTeamId(match.home) : onAway ? reportTeamId(match.away) : undefined; return { reported_home_cups: variables.data.home_cups, reported_away_cups: variables.data.away_cups, reported_ot_count: variables.data.ot_count, reported_by_team: callerTeamId, reported_by_player: userId, }; }, }); export const useConfirmMatchScore = (options?: MatchMutationOptions) => useOptimisticMatchMutation({ mutationFn: confirmMatchScore, showSuccessToast: false, ...options, buildPatch: (match) => ({ status: "ended", home_cups: match.reported_home_cups ?? match.home_cups, away_cups: match.reported_away_cups ?? match.away_cups, ot_count: match.reported_ot_count ?? match.ot_count, reported_home_cups: undefined, reported_away_cups: undefined, reported_ot_count: undefined, reported_by_team: undefined, reported_by_player: undefined, }), }); export const useClearMatchReport = (options?: MatchMutationOptions) => useOptimisticMatchMutation({ mutationFn: clearMatchReport, ...options, buildPatch: () => ({ reported_home_cups: undefined, reported_away_cups: undefined, reported_ot_count: undefined, reported_by_team: undefined, reported_by_player: undefined, }), });