import { Suspense, forwardRef, lazy, useEffect } from "react";

import { yieldMainThread } from "./yieldMainThread.js";
import { useSuspenseLoaded } from "../hooks/useSuspendedComponentsMounted.js";

import type { ComponentProps, ComponentType, SuspenseProps } from "react";

/**
 * When using Preact, Preact is rendering the initial render cycle synchronously, instead of async. This could lead
 * to long tasks in the JavaScript main thread. A lot of tutorials are recommending Code Splitting by using `React.lazy`.
 * `React.lazy` is the right direction, but long tasks could not be splitted into multiple tasks by this. We need to combine
 * this with `setTimeout(, 0)` to yield the main thread.
 *
 * Example usage:
 *
 * ```ts
 * const BannerContent = yieldLazyLoad(
 *    import("./content").then(({ BannerContent }) => BannerContent)
 * );
 * ```
 *
 * You could additionally combine this with `webpackMode: "eager"` to not create an own chunk for this import, instead resolve
 * the import through a `Promise`. Pro tip: If you are using inline-require (e.g. https://github.com/atlassian-labs/inline-require-webpack-plugin)
 * you can also use something like this: `yieldLazyLoad(Promise.resolve(MyBlocker))`.
 *
 * If you pass a `name`, it gets automatically reported to a `SuspenseLoaded` context. See also `useSuspendedComponentsMounted`.
 *
 * Attention: When using `useEffect()` together with `ref` for a yielded component, keep in mind that `ref.current` can be `null`.
 * Instead, use `<MyYieldedComponent ref={useCallback(node => ...)}`.
 *
 * @see https://github.com/preactjs/preact/pull/3386
 * @see https://web.dev/optimize-long-tasks/
 * @see https://preactjs.com/guide/v10/hooks/
 * @see https://webpack.js.org/api/module-methods/#webpackmode
 */
function yieldLazyLoad<T extends ComponentType<any>>(
    p: Promise<T>,
    name?: string,
    suspenseProps: Omit<SuspenseProps, "children"> = { fallback: null },
) {
    const lazyLoaded = lazy<T>(() => p.then((d) => yieldMainThread({ default: d })));
    const LazyLoaded = lazyLoaded as any;

    return forwardRef((props: ComponentProps<typeof lazyLoaded>, ref) => {
        const { onMounted } = useSuspenseLoaded();

        if (name) {
            useEffect(() => {
                onMounted?.(name);
            }, []);
        }

        return (
            <Suspense {...suspenseProps}>
                <LazyLoaded {...props} ref={ref} />
            </Suspense>
        );
    });
}

export { yieldLazyLoad };
