import { useEffect } from "react";

import type { DependencyList, EffectCallback } from "react";

type EffectCallbackReturn = ReturnType<EffectCallback>;

/**
 * A custom React hook that behaves similarly to `useEffect` but allows for async
 * operations and provides an `AbortController` to cancel these operations.
 */
function useAsyncEffect(
    effect: (options: {
        abortController: AbortController;
        aborted: () => boolean;
    }) => EffectCallbackReturn | Promise<EffectCallbackReturn>,
    deps: DependencyList,
) {
    useEffect(() => {
        const abortController = new AbortController();
        const disposeFns: Array<() => void> = [() => abortController.abort()];

        const runEffect = async () => {
            try {
                const dispose = await effect({ abortController, aborted: () => abortController.signal.aborted });
                if (typeof dispose === "function") {
                    disposeFns.push(dispose);
                }
            } catch (error) {
                if (error.name !== "AbortError") {
                    // Handle or re-throw errors that are not abort errors
                    throw error;
                }
            }
        };
        runEffect();

        return () => disposeFns.forEach((d) => d());
    }, deps);
}

export { useAsyncEffect };
