import { Modal as AntdModal } from "ant-design-vue";
import { CloseOutlined } from "@ant-design/icons-vue";
import {
  defineComponent,
  inject,
  onBeforeUnmount,
  onMounted,
  provide,
  reactive,
  ref,
} from "vue";
import { bool, number } from "vue-types";
import { useRouter } from "vue-router";
import "./Modal.less";
import ModalConfirm from "./ModalConfirm";
import ModalInput from "./ModalInput";

const store = reactive({
  modalKeys: [] as Array<{ key: number; visible: boolean }>,
  modalMaps: new Map() as Map<number, any>,
});

const MyModal = defineComponent({
  props: {
    visible: bool().isRequired,
    modalKey: number().isRequired,
  },
  setup(props) {
    provide("modalContext", props.modalKey);
    const closeRef = ref();
    const {
      context,
      children,
      closable,
      title,
      fullscreen,
      width,
      ...otherProps
    } = store.modalMaps.get(props.modalKey);
    const baseProps: any = {
      width,
      closable,
    };
    const isSelfWidth = !(width && !fullscreen);

    if (context) {
      Object.entries(context).forEach(([key, value]) => {
        provide(key, value);
      });
    }

    if (isSelfWidth) {
      baseProps.closable = false;
      baseProps.width = "auto";
      baseProps.class =
        "inf-modal" + (fullscreen ? " ant-modal-fullscreen" : "");
      onMounted(() => {
        const closeEl = closeRef.value;
        if (closeEl) {
          const maskEl = closeEl.parentElement.parentElement;
          // maskEl.addEventListener("click", closeModal, { once: true }); 选择文件过后,会触发事件但被拦截
          maskEl.addEventListener("click", closeModal);
        }
      });
      onBeforeUnmount(() => {
        const closeEl = closeRef.value;
        if (closeEl) {
          const maskEl = closeEl.parentElement.parentElement;
          maskEl.removeEventListener("click", closeModal);
        }
      });
    } else {
      baseProps.title = title;
    }

    function closeModal(e: MouseEvent) {
      if (e.target === e.currentTarget) {
        otherProps.onCancel?.();
      }
    }

    const RenderSelfHeader = () => (
      <>
        {closable ? (
          <CloseOutlined
            class="ant-modal-close"
            onClick={() => otherProps.onCancel()}
            ref={closeRef}
          />
        ) : null}
        {title ? <div class="ant-modal-header">{title}</div> : null}
      </>
    );

    return () => {
      return (
        <AntdModal
          key={props.modalKey}
          visible={props.visible}
          footer={null}
          {...baseProps}
          {...otherProps}
        >
          {isSelfWidth ? RenderSelfHeader() : null}
          {children}
        </AntdModal>
      );
    };
  },
});

function hidden(key: number) {
  if (key) {
    const modal = store.modalKeys.find((item) => item.key === key);
    modal && (modal.visible = false);
  }
}

const Modal = {
  show(
    children: JSX.Element,
    config?: {
      width?: string;
      title?: string;
      afterClose?: VoidFunction;
      closable?: boolean;
      fullscreen?: boolean;
      onOk?: any;
      onCancel?: any;
      [name: string]: any;
    },
    context?: {
      [name: string]: any;
    }
  ) {
    return new Promise((resolve, reject) => {
      const key = Date.now();
      const { afterClose, ...otherConfig } = config || {};
      store.modalMaps.set(key, {
        context,
        children,
        closable: true,
        afterClose: () => {
          const itemIndex = store.modalKeys.findIndex(
            (item) => item.key === key
          );
          if (itemIndex != -1) {
            store.modalKeys.splice(itemIndex, 1);
            store.modalMaps.delete(key);
          }
          afterClose?.();
        },
        ...otherConfig,
        onOk: async (value?: any) => {
          if (otherConfig.onOk) {
            await otherConfig.onOk(value);
          }
          resolve(value);
          hidden(key);
        },
        onCancel: (msg?: string) => {
          reject(console.warn(msg));
          hidden(key);
        },
      });
      store.modalKeys.push({ key, visible: true });
    });
  },
  use() {
    const key = inject("modalContext") as number;
    const ctx = store.modalMaps.get(key);
    return {
      submit: ctx.onOk as (value?: any) => void,
      cancel: ctx.onCancel as (msg?: any) => void,
    };
  },
  input(params: {
    title?: string;
    defaultValue?: string;
    placeholder?: string;
    okText?: string;
    cancelText?: string;
    selection?: boolean;
    onValid?: (value: string) => Promise<boolean>;
    [name: string]: any;
  }) {
    const {
      title,
      defaultValue,
      placeholder,
      maxLength,
      okText,
      cancelText,
      selection,
      onValid,
      ...otherParams
    } = params;
    return Modal.show(
      <ModalInput
        defaultValue={defaultValue}
        placeholder={placeholder}
        maxLength={maxLength}
        selection={selection}
        okText={okText}
        cancelText={cancelText}
      />,
      {
        width: "400px",
        title: title,
        closable: true,
        ...otherParams,
        onOk: async (v: any) => {
          const isValid = await (onValid ? onValid(v) : true);
          if (isValid) return v;
          throw console.warn({ msg: "invalid input value", silence: true });
        },
      }
    );
  },

  confirm(params: {
    title?: string;
    content?: any;
    okText?: string;
    refuseText?: string;
    cancelText?: string;
    type?: "danger";
    [name: string]: any;
  }) {
    const { type, okText, refuseText, cancelText, content, ...otherParams } =
      params;
    return Modal.show(
      <ModalConfirm
        type={type}
        content={content}
        okText={okText}
        refuseText={refuseText}
        cancelText={cancelText}
      />,
      {
        width: "400px",
        ...otherParams,
      }
    );
  },
  clear() {
    store.modalKeys = [];
    store.modalMaps.clear();
  },
};

export const ModalProvider = defineComponent(() => {
  // 路由跳转前销毁当前页面的弹层
  const router = useRouter();
  router.beforeEach(() => {
    Modal.clear();
  });

  return () =>
    store.modalKeys.map(({ key, visible }) => (
      <MyModal key={key} visible={visible} modalKey={key} />
    ));
});

export default Modal;
