import type { Api } from "Api";
import { FetchPromise, type ModuleApi, type ResponseObject } from "modules/ModuleApi";
import useSWR, { type SWRConfiguration, useSWRConfig, type Middleware, type SWRHook } from "swr";
import useSWRInfinite from "swr/infinite";
import { useApi } from "../ClientProvider";
import { useState } from "react";

export const useApiQuery = <
   Data,
   RefineFn extends (data: ResponseObject<Data>) => unknown = (data: ResponseObject<Data>) => Data,
>(
   fn: (api: Api) => FetchPromise<Data>,
   options?: SWRConfiguration & { enabled?: boolean; refine?: RefineFn },
) => {
   const api = useApi();
   const promise = fn(api);
   const refine = options?.refine ?? ((data: any) => data);
   const fetcher = () => promise.execute().then(refine);
   const key = promise.key();

   type RefinedData = RefineFn extends (data: ResponseObject<Data>) => infer R ? R : Data;

   const swr = useSWR<RefinedData>(options?.enabled === false ? null : key, fetcher, options);
   return {
      ...swr,
      promise,
      key,
      api,
   };
};

/** @attention: highly experimental, use with caution! */
export const useApiInfiniteQuery = <
   Data,
   RefineFn extends (data: ResponseObject<Data>) => unknown = (data: ResponseObject<Data>) => Data,
>(
   fn: (api: Api, page: number) => FetchPromise<Data>,
   options?: SWRConfiguration & { refine?: RefineFn; pageSize?: number },
) => {
   const [endReached, setEndReached] = useState(false);
   const api = useApi();
   const promise = (page: number) => fn(api, page);
   const refine = options?.refine ?? ((data: any) => data);

   type RefinedData = RefineFn extends (data: ResponseObject<Data>) => infer R ? R : Data;

   // @ts-ignore
   const swr = useSWRInfinite<RefinedData>(
      (index, previousPageData: any) => {
         if (index > 0 && previousPageData && previousPageData.length < (options?.pageSize ?? 0)) {
            setEndReached(true);
            return null; // reached the end
         }
         return promise(index).request.url;
      },
      (url: string) => {
         return new FetchPromise(new Request(url), { fetcher: api.fetcher }, refine).execute();
      },
      {
         revalidateFirstPage: false,
      },
   );
   // @ts-ignore
   const data = swr.data ? [].concat(...swr.data) : [];
   return {
      ...swr,
      _data: swr.data,
      data,
      endReached,
      promise: promise(swr.size),
      key: promise(swr.size).key(),
      api,
   };
};

export const useInvalidate = (options?: { exact?: boolean }) => {
   const mutate = useSWRConfig().mutate;
   const api = useApi();

   return async (arg?: string | ((api: Api) => FetchPromise<any> | ModuleApi<any>)) => {
      let key = "";
      if (typeof arg === "string") {
         key = arg;
      } else if (typeof arg === "function") {
         key = arg(api).key();
      }

      if (options?.exact) return mutate(key);
      return mutate((k) => typeof k === "string" && k.startsWith(key));
   };
};

const mountOnceCache = new Map<string, any>();

/**
 * Simple middleware to only load on first mount.
 */
export const mountOnce: Middleware = (useSWRNext: SWRHook) => (key, fetcher, config) => {
   if (typeof key === "string") {
      if (mountOnceCache.has(key)) {
         return useSWRNext(key, fetcher, {
            ...config,
            revalidateOnMount: false,
         });
      }
      const swr = useSWRNext(key, fetcher, config);
      if (swr.data) {
         mountOnceCache.set(key, true);
      }
      return swr;
   }
   return useSWRNext(key, fetcher, config);
};
