import { router } from "@/router";
import { createVNode, reactive, render, watch, watchEffect } from "vue";
import Locale from "@/config/locale.vue";
import type { App, Ref, VNode } from "vue";

interface ComponentConstructor<P = any> {
  new (...args: any[]): {
    $props: P;
  };
}
type Mutable<Type> = {
  -readonly [Key in keyof Type]: Type[Key];
};

export type MountContext<P = any> = {
  props?: P;
  children?: unknown;
  element?: HTMLElement;
  app?: App;
};
function mount<P>(
  component: Readonly<P>,
  { props, children, element, app }: MountContext<P> = {}
) {
  let el = element;
  let vNode: VNode | undefined = createVNode(
    Locale,
    {},
    {
      default: () => createVNode(component, props as any, children),
    }
  );

  if (app && app._context) vNode.appContext = app._context;
  if (el) {
    render(vNode, el);
  } else if (typeof document !== "undefined") {
    render(vNode, (el = document.createElement("div")));
  }

  const destroy = () => {
    if (el) render(null, el);
    el = undefined;
    vNode = undefined;
  };

  return { vNode, destroy, el };
}

let app: App;
export const setApp = (application: App) => (app = application);

export const extendProps = <T extends {}, E extends {}>(
  origin: T,
  append: E
): T & E => {
  const props = reactive({ ...append }) as T & E;
  watchEffect(() => {
    for (const key in origin) {
      (props as any)[key] = origin[key];
    }
  });
  return props;
};

export const mountComponent = <P>(
  comp: ComponentConstructor<P>,
  props: Mutable<P>,
  children?: any
) => {
  const element = document.createElement("div");
  const { destroy: destroyRaw } = mount(comp, {
    element,
    props,
    app: app,
    children,
  } as any);
  const destroy = () => {
    destroyRaw();
    if (document.body.contains(element)) {
      document.body.removeChild(element);
    }
    stopWatch();
  };

  const stopWatch = watch(() => router.currentRoute.value.name, destroy);

  return destroy;
};

import Dialog from "@/components/dialog/index.vue";
import { DialogProps, dialogPropsKeys } from "@/components/dialog/type";

export type QuiskExpose = {
  submit?: () => void;
  quit?: () => void;
} & Partial<{ [key in keyof DialogProps]?: Ref<DialogProps[key]> }>;

export const quiskMountFactory =
  <P>(comp: ComponentConstructor<P>, dprops: DialogProps) =>
  <T = boolean>(
    props: Mutable<P>,
    dRef?: (expose: { quit: () => void; submit: () => void }) => void
  ): Promise<T> => {
    let ref: QuiskExpose;

    return new Promise((resolve) => {
      const api = {
        onQuit: async () => {
          const ret = ref.quit && ((await ref.quit()) as any);
          if (ret) {
            resolve(ret);
          } else {
            resolve(false as any);
          }
          destroy();
        },
        onSubmit: async () => {
          const ret = ref.submit && ((await ref.submit()) as any);
          if (ret) {
            resolve(ret);
          } else {
            resolve(true as any);
          }
          destroy();
        },
      };

      const layoutProps = reactive({
        ...dprops,
        ref: undefined,
        show: true,
        ...api,
      });
      const destroy = mountComponent(Dialog, layoutProps, {
        default: () =>
          createVNode(comp, {
            ...props,
            ref: (v: any) => {
              for (const key in v) {
                if (dialogPropsKeys.includes(key as any)) {
                  (layoutProps as any)[key] = v[key];
                }
              }
              ref = v;
            },
          }),
      });

      dRef &&
        dRef({
          submit: () => api.onSubmit(),
          quit: () => api.onQuit(),
        });
    });
  };
