"use client";

import { createContext, useContext, useEffect, useMemo, useState } from "react";
import GlobalLoader from "@/components/ui/GlobalLoader";

type ApiLoadingContextValue = {
  isLoading: boolean;
};

const ApiLoadingContext = createContext<ApiLoadingContextValue>({ isLoading: false });

/** In-flight request cache: same method+url+body shares one network request; each caller gets a cloned response. */
const inFlightMap = new Map<string, Promise<Response>>();

function getRequestKey(input: RequestInfo | URL, init?: RequestInit): string {
  const url = typeof input === "string" ? input : input instanceof Request ? input.url : input.toString();
  const method = (typeof input === "object" && input instanceof Request ? input.method : init?.method) ?? "GET";
  const body = (typeof input === "object" && input instanceof Request ? input.body : init?.body) ?? undefined;
  const bodyKey = body == null ? "" : typeof body === "string" ? body : `_${Date.now()}_${Math.random()}`;
  return `${method}|${url}|${bodyKey}`;
}

export function ApiLoadingProvider({ children }: { children: React.ReactNode }) {
  const [requestCount, setRequestCount] = useState(0);

  useEffect(() => {
    if (typeof window === "undefined") return;

    const globalWindow = window as Window & {
      __oxyfinz_fetch_wrapped__?: boolean;
      __oxyfinz_original_fetch__?: typeof window.fetch;
    };

    if (globalWindow.__oxyfinz_fetch_wrapped__) {
      return;
    }

    globalWindow.__oxyfinz_original_fetch__ =
      globalWindow.__oxyfinz_original_fetch__ ?? globalWindow.fetch;
    const baseFetch = globalWindow.__oxyfinz_original_fetch__;

    let mounted = true;

    const wrappedFetch: typeof window.fetch = async (input, init) => {
      if (!mounted) {
        return baseFetch(input, init);
      }

      const key = getRequestKey(input, init);
      const existing = inFlightMap.get(key);

      if (existing) {
        return existing.then((res) => res.clone());
      }

      const promise = baseFetch(input, init).then(
        (res) => {
          inFlightMap.delete(key);
          return res;
        },
        (err) => {
          inFlightMap.delete(key);
          throw err;
        }
      );
      inFlightMap.set(key, promise);

      // Increment synchronously — mounted is guaranteed true here (checked above,
      // and JS is single-threaded so nothing can unmount between that check and here).
      setRequestCount((prev) => prev + 1);
      try {
        const response = await promise;
        return response.clone();
      } finally {
        // Always decrement to keep the counter balanced, even if the component
        // unmounted while the request was in-flight. React 18 silently ignores
        // state updates on unmounted components, so this is safe.
        setRequestCount((prev) => Math.max(prev - 1, 0));
      }
    };

    globalWindow.fetch = wrappedFetch as typeof window.fetch;
    globalWindow.__oxyfinz_fetch_wrapped__ = true;

    return () => {
      mounted = false;
      globalWindow.fetch = baseFetch;
      globalWindow.__oxyfinz_fetch_wrapped__ = false;
    };
  }, []);

  const value = useMemo(
    () => ({
      isLoading: requestCount > 0,
    }),
    [requestCount]
  );

  return (
    <ApiLoadingContext.Provider value={value}>
      {children}
      {value.isLoading && <GlobalLoader />}
    </ApiLoadingContext.Provider>
  );
}

export const useApiLoading = () => useContext(ApiLoadingContext);
