import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMemo, useState } from "react"; import { useSelectedAccountId } from "./account-selection"; import { MAP_TYPES, WORLD_VIEWPORT, boundsContainFeature, featureMatchesSearch, fetchMap, type MapViewport, } from "./network-map-model"; import { fetchQueryEnvelope } from "#/lib/api-client"; import type { NetworkMapKind } from "#/lib/network-map"; import { queryKeys } from "#/lib/query-client"; import type { NetworkMapRouteSearch, RouteSearchChange, } from "#/lib/route-search"; export function useNetworkMapController( filters: NetworkMapRouteSearch, onFiltersChange: RouteSearchChange, ) { const queryClient = useQueryClient(); const { type, q: visibleSearch } = filters; const [viewport, setViewport] = useState(WORLD_VIEWPORT); const statusQuery = useQuery({ queryKey: queryKeys.status, queryFn: ({ signal }) => fetchQueryEnvelope({ signal }), }); const selectedAccountId = useSelectedAccountId(statusQuery.data?.accounts); const mapQueryKey = [ ...queryKeys.networkMap, { type, selectedAccountId: selectedAccountId ?? null }, ] as const; const mapQuery = useQuery({ queryKey: mapQueryKey, queryFn: ({ signal }) => fetchMap(type, false, selectedAccountId, signal), staleTime: 5 * 60_000, }); const refreshMutation = useMutation({ mutationFn: () => fetchMap(type, true, selectedAccountId), onSuccess: (nextData) => { queryClient.setQueryData(mapQueryKey, nextData); }, }); const data = mapQuery.data ?? null; const loading = mapQuery.isPending || refreshMutation.isPending; const queryError = refreshMutation.error ?? mapQuery.error; const visibleFeatures = useMemo( () => (data?.features ?? []) .slice() .filter((feature) => boundsContainFeature(viewport.bounds, feature)) .sort( (a, b) => b.properties.followersCount - a.properties.followersCount || a.properties.handle.localeCompare(b.properties.handle), ), [data, viewport], ); const filteredVisibleFeatures = useMemo( () => visibleFeatures .filter((feature) => featureMatchesSearch(feature, visibleSearch)) .slice(0, 160), [visibleFeatures, visibleSearch], ); return { type, setType: (value: NetworkMapKind) => onFiltersChange({ ...filters, type: value }), viewport, setViewport, visibleSearch, setVisibleSearch: (value: string) => onFiltersChange({ ...filters, q: value }, { replace: true }), data, loading, error: queryError ? queryError instanceof Error ? queryError.message : "Map unavailable" : null, refresh: refreshMutation.mutate, visibleFeatures, filteredVisibleFeatures, mapTypes: MAP_TYPES, }; }