import type { QueryKey } from "@tanstack/react-query";

import { queryClient } from "./queryClient";
import { readCachedValue, writeCachedValue } from "./offlineCache";

const inFlightRequests = new Map<string, Promise<unknown>>();

export function normalizeTeamSnapCacheScope(scope?: string | number | null): string {
  const value = scope == null ? "" : String(scope).trim();
  return value || "anonymous";
}

function buildCacheParts(scope: string | number | null | undefined, resource: string, parts: readonly unknown[]): readonly unknown[] {
  return ["teamsnap", normalizeTeamSnapCacheScope(scope), resource, ...parts];
}

function cacheKey(parts: readonly unknown[]): string {
  return JSON.stringify(parts);
}

function startFreshFetch<T>(
  cacheParts: readonly unknown[],
  queryKey: QueryKey,
  fetchFresh: () => Promise<T>,
): Promise<T> {
  const key = cacheKey(cacheParts);
  const existing = inFlightRequests.get(key);
  if (existing) {
    return existing as Promise<T>;
  }

  const request = fetchFresh()
    .then((data) => {
      writeCachedValue(cacheParts, data);
      queryClient.setQueryData(queryKey, data);
      return data;
    })
    .finally(() => {
      inFlightRequests.delete(key);
    });

  inFlightRequests.set(key, request);
  return request;
}

export async function staleWhileRevalidateTeamSnap<T>({
  cacheParts,
  queryKey,
  fetchFresh,
}: {
  cacheParts: readonly unknown[];
  queryKey: QueryKey;
  fetchFresh: () => Promise<T>;
}): Promise<T> {
  const cached = readCachedValue<T>(cacheParts);
  if (cached) {
    void startFreshFetch(cacheParts, queryKey, fetchFresh).catch(() => undefined);
    return cached.data;
  }

  return startFreshFetch(cacheParts, queryKey, fetchFresh);
}

export const teamSnapQueryKeys = {
  me(scope?: string | number | null): QueryKey {
    return buildCacheParts(scope, "me", []);
  },
  teams(scope?: string | number | null): QueryKey {
    return buildCacheParts(scope, "teams", []);
  },
  members(scope: string | number | null | undefined, teamId: string): QueryKey {
    return buildCacheParts(scope, "members", [teamId]);
  },
  events(scope: string | number | null | undefined, teamId: string): QueryKey {
    return buildCacheParts(scope, "events", [teamId]);
  },
  availabilities(scope: string | number | null | undefined, teamId: string, eventId?: string): QueryKey {
    return buildCacheParts(scope, "availabilities", [teamId, eventId ?? ""]);
  },
  assignments(scope: string | number | null | undefined, teamId: string, eventId?: string): QueryKey {
    return buildCacheParts(scope, "assignments", [teamId, eventId ?? ""]);
  },
  eventLineup(scope: string | number | null | undefined, teamId: string, eventId: string): QueryKey {
    return buildCacheParts(scope, "eventLineup", [teamId, eventId]);
  },
};
