• Provides rate limit context to its children, managing rate limit state and notifications.

    Parameters

    • children: Readonly<{ children: ReactNode }>

      The React children to be wrapped by the provider.

    Returns Element

    The rate limit context provider with value for consumers.

    export function RateLimitProvider({
    children,
    }: Readonly<{ children: ReactNode }>) {
    const [rateLimitState, setRateLimitState] = useState<RateLimitState>({
    isRateLimited: false,
    retryAfter: null,
    message: null,
    });

    // Use string type only for toast ID to fix TypeScript error
    const [toastId, setToastId] = useState<string | null>(null);
    const { registerStateInspector: registerRateLimitInspector } = useDebug();
    const rateLimitInspectorHandleRef =
    useRef<StateInspectorHandle<RateLimitDebugSnapshot> | null>(null);
    const rateLimitSnapshotRef = useRef<RateLimitDebugSnapshot | null>(null);
    const getRateLimitSnapshotRef = useRef<() => RateLimitDebugSnapshot>(() => ({
    rateLimitState,
    toastId,
    }));
    getRateLimitSnapshotRef.current = () => ({ rateLimitState, toastId });

    const emitRateLimitSnapshot = useCallback(() => {
    if (!rateLimitInspectorHandleRef.current) return;
    const snapshot = getRateLimitSnapshotRef.current();
    rateLimitSnapshotRef.current = snapshot;
    rateLimitInspectorHandleRef.current.publish(snapshot);
    }, []);

    const applyRateLimitDebugSnapshot = useCallback(
    (snapshot: RateLimitDebugSnapshot) => {
    if (snapshot.rateLimitState) {
    setRateLimitState(snapshot.rateLimitState);
    }
    setToastId(snapshot.toastId ?? null);
    },
    [],
    );

    // Function to set rate limit state
    const setRateLimit = (
    isLimited: boolean,
    retryTime?: number,
    message?: string,
    ) => {
    const retryTimestamp = retryTime ? Date.now() + retryTime * 1000 : null;

    console.log("Setting rate limit state:", {
    isLimited,
    retryTimestamp,
    message,
    });

    setRateLimitState({
    isRateLimited: isLimited,
    retryAfter: retryTimestamp,
    message:
    message ||
    "AniList API rate limit reached. Please wait before making more requests.",
    });
    };

    // Function to clear rate limit state
    const clearRateLimit = () => {
    setRateLimitState({
    isRateLimited: false,
    retryAfter: null,
    message: null,
    });
    };

    // Periodically check rate limit status from main process
    useEffect(() => {
    // Skip if we're in a browser environment without Electron
    if (!globalThis.electronAPI?.anilist?.getRateLimitStatus) return;

    const checkRateLimitStatus = async () => {
    try {
    const status =
    await globalThis.electronAPI.anilist.getRateLimitStatus();

    if (status.isRateLimited) {
    setRateLimitState({
    isRateLimited: true,
    retryAfter: status.retryAfter,
    message:
    "AniList API rate limit reached. Please wait before making more requests.",
    });
    } else if (rateLimitState.isRateLimited) {
    // Clear rate limit if it was previously set but is now cleared
    clearRateLimit();
    }
    } catch (error) {
    console.error("Error checking rate limit status:", error);
    }
    };

    // Check immediately on component mount
    checkRateLimitStatus();

    // Then check periodically
    const interval = setInterval(checkRateLimitStatus, 1000);

    return () => clearInterval(interval);
    }, [rateLimitState.isRateLimited]);

    // Listen for global rate limiting events
    useEffect(() => {
    const handleRateLimit = (event: Event) => {
    const customEvent = event as CustomEvent;
    if (customEvent.detail) {
    const { retryAfter, message } = customEvent.detail;
    console.log("Received rate limit event:", customEvent.detail);
    setRateLimit(true, retryAfter, message);
    }
    };

    // Add event listener for the custom rate limiting event
    globalThis.addEventListener("anilist:rate-limited", handleRateLimit);

    // Clean up the listener on unmount
    return () => {
    globalThis.removeEventListener("anilist:rate-limited", handleRateLimit);
    };
    }, []);

    // Effect to show/hide toast notification based on rate limit state
    useEffect(() => {
    if (rateLimitState.isRateLimited && rateLimitState.retryAfter) {
    // Create a dismissible persistent toast
    const id = toast.warning(
    <RateLimitToast
    message={rateLimitState.message || "Rate limited by AniList API"}
    retryAfter={rateLimitState.retryAfter}
    onComplete={clearRateLimit}
    />,
    {
    id: "rate-limit-toast",
    duration: Infinity, // Don't auto-dismiss
    },
    );

    // Force cast to string to fix TypeScript error
    setToastId(id as unknown as string);
    } else if (toastId) {
    // Dismiss the toast when no longer rate limited
    toast.dismiss(toastId);
    setToastId(null);
    }
    }, [rateLimitState.isRateLimited, rateLimitState.retryAfter]);

    useEffect(() => {
    emitRateLimitSnapshot();
    }, [rateLimitState, toastId, emitRateLimitSnapshot]);

    useEffect(() => {
    if (!registerRateLimitInspector) return;

    rateLimitSnapshotRef.current = getRateLimitSnapshotRef.current();

    const handle = registerRateLimitInspector<RateLimitDebugSnapshot>({
    id: "rate-limit-state",
    label: "Rate Limit",
    description:
    "AniList API rate limit flags, retry timestamp, and active toast identifier.",
    group: "Application",
    getSnapshot: () =>
    rateLimitSnapshotRef.current ?? getRateLimitSnapshotRef.current(),
    setSnapshot: applyRateLimitDebugSnapshot,
    });

    rateLimitInspectorHandleRef.current = handle;

    return () => {
    handle.unregister();
    rateLimitInspectorHandleRef.current = null;
    rateLimitSnapshotRef.current = null;
    };
    }, [registerRateLimitInspector, applyRateLimitDebugSnapshot]);

    const contextValue = React.useMemo(
    () => ({ rateLimitState, setRateLimit, clearRateLimit }),
    [rateLimitState, setRateLimit, clearRateLimit],
    );

    return (
    <RateLimitContext.Provider value={contextValue}>
    {children}
    </RateLimitContext.Provider>
    );
    }