import type { Component, DeepReadonly, Ref, ShallowRef } from 'vue';

import { markRaw, nextTick, readonly, ref, shallowRef } from 'vue';

interface LazyComponentModule {
  default: Component;
}

interface LazyComponentController<Instance extends object> {
  component: ShallowRef<Component | undefined>;
  componentRef: ShallowRef<Instance | undefined>;
  ensureInstance: () => Promise<Instance>;
  loading: DeepReadonly<Ref<boolean>>;
}

/**
 * 延迟加载并挂载通过模板 ref 调用的组件。
 *
 * @param loader 组件模块加载函数。
 * @returns 延迟组件、组件实例引用、加载状态和实例获取函数。
 */
function createLazyComponentController<Instance extends object>(
  loader: () => Promise<LazyComponentModule>,
): LazyComponentController<Instance> {
  const component = shallowRef<Component>();
  const componentRef = shallowRef<Instance>();
  const loading = ref(false);
  let loadPromise: Promise<Component> | undefined;
  let mountPromise: Promise<Instance> | undefined;

  async function loadComponent() {
    if (component.value) {
      return component.value;
    }
    if (!loadPromise) {
      loadPromise = loader()
        .then((module) => {
          if (!module.default) {
            throw new Error('Lazy component module has no default export');
          }
          return markRaw(module.default);
        })
        .catch((error) => {
          loadPromise = undefined;
          throw error;
        });
    }
    const resolvedComponent = await loadPromise;
    component.value = resolvedComponent;
    return resolvedComponent;
  }

  function ensureInstance() {
    if (componentRef.value) {
      return Promise.resolve(componentRef.value);
    }
    if (mountPromise) {
      return mountPromise;
    }
    loading.value = true;
    mountPromise = loadComponent()
      .then(async () => {
        await nextTick();
        if (!componentRef.value) {
          throw new Error('Lazy component failed to mount');
        }
        return componentRef.value;
      })
      .finally(() => {
        loading.value = false;
        mountPromise = undefined;
      });
    return mountPromise;
  }

  return {
    component,
    componentRef,
    ensureInstance,
    loading: readonly(loading),
  };
}

export { createLazyComponentController };
