"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { applyOptimisticCartQuantity } from "@/lib/storefront-cart-optimistic";
import { dispatchStorefrontCartUpdated } from "@/lib/storefront-events";
import type { StorefrontCart } from "@/types/storefront";

type CartState = StorefrontCart | null;

export function useOptimisticCartQuantity<T extends CartState>({
  setCart,
  onError,
}: {
  setCart: React.Dispatch<React.SetStateAction<T>>;
  onError?: (message: string) => void;
}) {
  const pendingQuantitiesRef = useRef<Record<string, number>>({});
  const debounceTimersRef = useRef<Record<string, number>>({});
  const requestTokensRef = useRef<Record<string, number>>({});
  const [syncingKeys, setSyncingKeys] = useState<Record<string, boolean>>({});

  const markSyncing = useCallback((key: string, value: boolean) => {
    setSyncingKeys((current) => {
      if (value) {
        return { ...current, [key]: true };
      }

      if (!(key in current)) {
        return current;
      }

      const next = { ...current };
      delete next[key];
      return next;
    });
  }, []);

  const cancelItemQuantitySync = useCallback(
    (key: string) => {
      delete pendingQuantitiesRef.current[key];
      delete requestTokensRef.current[key];

      if (debounceTimersRef.current[key]) {
        window.clearTimeout(debounceTimersRef.current[key]);
        delete debounceTimersRef.current[key];
      }

      markSyncing(key, false);
    },
    [markSyncing],
  );

  const syncQuantity = useCallback(
    async (key: string) => {
      const quantity = pendingQuantitiesRef.current[key];

      if (!Number.isInteger(quantity) || quantity < 1) {
        markSyncing(key, false);
        return;
      }

      const token = (requestTokensRef.current[key] ?? 0) + 1;
      requestTokensRef.current[key] = token;

      try {
        const response = await fetch(`/api/cart/items/${encodeURIComponent(key)}`, {
          method: "PATCH",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ quantity }),
        });
        const payload = (await response.json()) as StorefrontCart | { message?: string };

        if (!response.ok) {
          throw new Error(
            "message" in payload && payload.message
              ? payload.message
              : "Ažuriranje košarice nije uspjelo.",
          );
        }

        const hasNewerIntent =
          pendingQuantitiesRef.current[key] !== quantity ||
          requestTokensRef.current[key] !== token;

        if (hasNewerIntent) {
          return;
        }

        delete pendingQuantitiesRef.current[key];
        markSyncing(key, false);
        setCart(payload as T);
        dispatchStorefrontCartUpdated(payload as StorefrontCart, "update-item");
      } catch (error) {
        if (requestTokensRef.current[key] !== token) {
          return;
        }

        markSyncing(key, false);
        onError?.(
          error instanceof Error
            ? error.message
            : "Ažuriranje košarice nije uspjelo.",
        );
      }
    },
    [markSyncing, onError, setCart],
  );

  const updateItemQuantity = useCallback(
    (key: string, quantity: number) => {
      if (!Number.isInteger(quantity) || quantity < 1) {
        return;
      }

      pendingQuantitiesRef.current[key] = quantity;
      markSyncing(key, true);

      let optimisticCartForDispatch: StorefrontCart | null = null;

      setCart((current) => {
        if (!current) {
          return current;
        }

        const optimisticCart = applyOptimisticCartQuantity(current, key, quantity);
        optimisticCartForDispatch = optimisticCart;
        return optimisticCart as T;
      });

      if (optimisticCartForDispatch) {
        window.setTimeout(() => {
          dispatchStorefrontCartUpdated(optimisticCartForDispatch as StorefrontCart, "update-item");
        }, 0);
      }

      if (debounceTimersRef.current[key]) {
        window.clearTimeout(debounceTimersRef.current[key]);
      }

      debounceTimersRef.current[key] = window.setTimeout(() => {
        void syncQuantity(key);
      }, 180);
    },
    [markSyncing, setCart, syncQuantity],
  );

  useEffect(() => {
    const timers = debounceTimersRef.current;

    return () => {
      for (const timer of Object.values(timers)) {
        window.clearTimeout(timer);
      }
    };
  }, []);

  return {
    isQuantitySyncing: (key: string) => Boolean(syncingKeys[key]),
    cancelItemQuantitySync,
    updateItemQuantity,
  };
}
