import type { FunctionalComponent } from 'preact';
import { useEffect, useState } from 'preact/hooks';

type LazyComponentState<Props> = {
  Component: FunctionalComponent<Props> | null;
  error: unknown;
};

export function createLazyComponent<Props, Module>(
  loadModule: () => Promise<Module>,
  selectComponent: (module: Module) => FunctionalComponent<Props>,
): FunctionalComponent<Props> {
  let cachedComponent: FunctionalComponent<Props> | null = null;
  let cachedError: unknown = null;
  let pending: Promise<FunctionalComponent<Props>> | null = null;

  const load = () => {
    if (cachedComponent) return Promise.resolve(cachedComponent);
    if (cachedError) return Promise.reject(cachedError);
    if (!pending) {
      pending = loadModule()
        .then((module) => {
          cachedComponent = selectComponent(module);
          return cachedComponent;
        })
        .catch((error) => {
          cachedError = error;
          throw error;
        });
    }
    return pending;
  };

  const LazyComponent: FunctionalComponent<Props> = (props) => {
    const [state, setState] = useState<LazyComponentState<Props>>({
      Component: cachedComponent,
      error: cachedError,
    });

    useEffect(() => {
      if (state.Component || state.error) return;
      let active = true;
      load().then(
        (Component) => {
          if (active) setState({ Component, error: null });
        },
        (error) => {
          if (active) setState({ Component: null, error });
        },
      );
      return () => {
        active = false;
      };
    }, [state.Component, state.error]);

    if (state.error) {
      return <div role="alert" data-asuka-lazy-component-error className="ask-text-xs ask-text-danger">Unable to load this editor.</div>;
    }
    if (!state.Component) {
      return <div aria-busy="true" data-asuka-lazy-component-loading className="ask-min-h-8 ask-w-full" />;
    }

    const Component = state.Component;
    return <Component {...props} />;
  };

  return LazyComponent;
}
