import { useEffect, useRef, useState } from 'react';

import type { ComponentProps, FC, NamedExoticComponent } from 'react';

import retry from '~/src/lib/utils/retry';
import { useAppLoading } from '../NextApp/lib/CoreUi';

/**
 * A placeholder component that loads the actual component async
 * when it's first rendered. This means that you can reduce core bundle
 * sizes and load JS only if/when it's needed.
 *
 * This is largely the same as `next/dynamic` but instead it has improved
 * callbacks for loading state. By default it shows the AppLoading indicator
 * and has inline callback to allow reacting to loading state change.
 *
 * WARN: This will not render serverside as the dynamic import is triggered
 * on mount. Use this for non-content UI like modals/dialogs.
 */
const LazyComponent = <TComponent extends FC | NamedExoticComponent>({
  loader,
  onLoadingChange = () => {},
  withAppLoading = true,
  ...componentProps
}: {
  loader: () => Promise<{ default: TComponent } | TComponent>;
  onLoadingChange?: (isLoading: boolean) => void;
  withAppLoading?: boolean;
} & ComponentProps<TComponent>) => {
  const LoadedComponentRef = useRef<FC>(null);
  const [isLoading, setIsLoading] = useState(true);
  const LoadedComponent = LoadedComponentRef.current;
  const setAppLoading = useAppLoading();

  useEffect(() => {
    onLoadingChange(isLoading);

    if (withAppLoading) {
      setAppLoading(true);
    }

    retry(loader).then((result) => {
      LoadedComponentRef.current = result['default'] || result;
      setIsLoading(false);

      if (withAppLoading) {
        setAppLoading(false);
      }
    });
  }, []);

  if (isLoading || !LoadedComponent) {
    return null;
  }

  return <LoadedComponent {...componentProps} />;
};

export default LazyComponent;
