<template>
  <div class="x-dialog__wrapper"
       tabindex="1"
       :style="wrapperStyle"
       @click="stopPropagationHandler"
       @mousedown="stopPropagationHandler">
    <div v-if="modal" @click="handleClickModal" class="x-dialog__modal"></div>
    <div class="x-dialog"
         :style="styles"
         :class="classes"
         ref="dialog"
         v-draggable="draggableProps"
         v-resizable="resizableProps"
         @mousedown="handleMouseDown">
      <div class="x-dialog__header">
        <div class="x-dialog__title">
          <XIcon v-if="icon" class="x-dialog__title-icon" :size="16" v-bind="titleIcon"></XIcon>
          <slot name="title">{{title}}</slot>
        </div>
        <div class="x-dialog__extend">
          <slot name="extend"></slot>
        </div>
        <div class="x-dialog__tools">
          <slot name="tools"></slot>
          <XIcon v-for="(tool, index) in tools"
                 :key="`dialog-tool-${index}`"
                 :size="12"
                 v-bind="tool"
                 @click="handleToolClick(tool)"></XIcon>
          <XIcon v-if="minimizable" :size="12" :icon="icons.Minimize" @click="setMode('minimized')"></XIcon>
          <XIcon v-if="maximizable" :size="12" :icon="currentMode === 'maximized' ? icons.Popup:icons.Maximize"
                 @click="togglePopup"></XIcon>
          <XIcon v-if="closable" :size="12" :icon="icons.Close" @click="close"></XIcon>
        </div>
      </div>
      <div class="x-dialog__body" ref="body" :class="{'is-no-padding':!this.bodyPadding}">
        <slot></slot>
      </div>
      <div v-if="$scopedSlots.footer || $slots.footer" class="x-dialog__footer" ref="footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</template>

<script>
/**
 * 弹窗组件
 * @module XDialog
 */
import { XIcon } from '@newpearl/ui';
import { Maximize, Minimize, Popup, Close } from '@newpearl/icons';
import Draggable from '@newpearl/ui/lib/directive/draggable';
import Resizable from '@newpearl/ui/lib/directive/resizable';
import { addResizeListener, removeResizeListener } from 'element-ui/lib/utils/resize-event';
import Keyboard from '@newpearl/utils/lib/Keyboard';

const ROOT_DIALOG_RESIZE_EVENT_NAME = 'root-dialog-resize';
let Z_INDEX = 1000;
/**
 * 插槽
 * @member slots
 * @property {string} [default] 默认插槽，窗体内容
 * @property {string} [title] 标题内容
 * @property {string} [extend] 扩展标题内容
 * @property {string} [tools] 工具按钮
 */

export default {
  name: 'XDialog',
  provide() {
    return {
      $dialog: this
    };
  },
  inject: {
    $admin: {
      default: null
    }
  },
  inheritAttrs: false,
  components: {
    XIcon
  },
  directives: {
    Draggable,
    Resizable
  },
  /**
   * 属性参数
   * @member props
   * @property {string|number} [name] 组件实例标识，用于记录实例到 $admin
   * @property {string} [title] 标题文本
   * @property {string|object} [icon] 标题图标
   * @property {boolean} [closable=true] 开放关闭按钮
   * @property {boolean} [maximizable=true] 开放最大化按钮
   * @property {boolean} [minimizable=true] 开放最小化按钮
   * @property {Object[]} [tools] 工具按钮配置, 参数项目与XIcon一致
   * @property {number} [width=600] 初始化宽度
   * @property {number} [height=500] 初始化高度
   * @property {number} [left=0] 初始化位置left
   * @property {number} [top=0] 初始化位置top
   * @property {string} [mode=popup] 初始窗体模式，可选值：popup、maximized、minimized
   * @property {boolean|object} [draggable=true] 拖拽配置
   * @property {boolean|object} [resizable=true] resize配置
   * @property {boolean} [center=true] 初始化居中，设置居中，将忽略left top 参数
   * @property {number} [defaultZIndex] 初始化层级
   * @property {boolean} [bodyPadding=true] 开启内边距
   * @property {object} [componentInstance] 挂在内容组件实例
   * @property {object} [route] 页面路由对象，仅在在$admin下有效
   * @property {boolean} [lighter] 浅颜色
   * @property {function} [beforeClose] 关闭前回调函数，return false 阻止关闭，支持Promise
   * @property {boolean} [modal=true] 显示遮罩层
   * @property {boolean} [closeOnClickModal=false] 点击遮罩层可关闭
   * @property {boolean} [appendToBody=true] 渲染dom到body
   * @property {boolean} [stopPropagation=true] 阻止鼠标事件冒泡
   * @property {boolean} [closeOnPressEscape=true] 是否可以通过按下 ESC 关闭 Dialog
   * @property {function} [getVxeTable] 获取VxeTable实例方法，默认从页面 .vxe-table 样式名称的dom获取
   *
   */
  props: {
    // 标识
    name: [String, Number],
    title: {
      type: String
    },
    icon: [String, Object],
    closable: {
      type: Boolean,
      default: true
    },
    maximizable: {
      type: Boolean,
      default: true
    },
    minimizable: {
      type: Boolean,
      default: true
    },
    tools: {
      type: Array
    },
    width: {
      type: Number,
      default: 600
    },
    height: {
      type: Number,
      default: 400
    },
    left: {
      type: Number,
      default: 0
    },
    top: {
      type: Number,
      default: 0
    },
    mode: {
      type: String,
      default: 'popup',
      validator(v) {
        return ['popup', 'maximized', 'minimized'].includes(v);
      }
    },
    draggable: {
      type: [Boolean, Object],
      default: true
    },
    resizable: {
      type: [Boolean, Object],
      default: true
    },
    center: {
      type: Boolean,
      default: true
    },
    defaultZIndex: {
      type: Number
    },
    bodyPadding: {
      type: Boolean,
      default: true
    },
    componentInstance: Object,
    route: {
      type: Object
    },
    lighter: {
      type: Boolean
    },
    beforeClose: Function,
    modal: {
      type: Boolean,
      default: true
    },
    closeOnClickModal: {
      type: Boolean,
      default: false
    },
    appendToBody: {
      type: Boolean,
      default: true
    },
    stopPropagation: {
      type: Boolean,
      default: true
    },
    // 是否可以通过按下 ESC 关闭 Dialog
    closeOnPressEscape: {
      type: Boolean,
      default: true
    },
    getVxeTable: {
      type: Function,
      default: () => document.querySelectorAll('.vxe-table')
    }
  },
  data() {
    this.icons = {
      Maximize,
      Minimize,
      Popup,
      Close
    };
    return {
      currentMode: this.mode,
      zIndex: this.defaultZIndex || ++Z_INDEX,
      wrapperRect: null,
      dragRange: null,
      realHeight: this.height,
      containerOffset: null
    };
  },
  computed: {
    titleIcon() {
      return typeof this.icon === 'string'
        ? {
          name: this.icon
        }
        : this.icon;
    },
    draggableProps() {
      return {
        handle: '.x-dialog__header',
        disabled: !this.draggable || this.currentMode === 'maximized',
        range: this.dragRange,
        ...this.draggable,
        onStartDrag: e => {
          this.updateRange(e);
          /**
           * 开始拖拽时触发
           * @event drag-start
           * @param {object} e 事件对象
           */
          this.$emit('drag-start', e);
        },
        onStopDrag: e => {
          /**
           * 结束拖拽时触发
           * @event drag-stop
           * @param {object} e 事件对象
           */
          this.$emit('drag-stop', e);
        },
        onDrag: e => {
          const { componentInstance } = this;
          if (componentInstance) {
            const dialogDrag = componentInstance.$options?.dialogDrag;
            dialogDrag && dialogDrag.call(componentInstance, e);
          }
          /**
           * 拖拽时触发
           * @event drag
           * @param {object} e 事件对象
           */
          this.$emit('drag', e);
        }
      };
    },
    resizableProps() {
      return {
        minWidth: 300,
        minHeight: 300,
        maxWidth: this.wrapperRect?.width,
        maxHeight: this.wrapperRect?.height,
        disabled: !this.resizable || this.currentMode === 'maximized',
        ...this.resizable,
        onStopResize: e => {
          this.updateRange(e);
          /**
           * 结束resize时触发
           * @event resize-stop
           * @param {object} e 事件对象
           */
          this.$emit('resize-stop', e);
          this.handleSizeChange(e);
        },
        onStartResize: e => {
          /**
           * 开始resize时触发
           * @event resize-start
           * @param {object} e 事件对象
           */
          this.$emit('resize-start', e);
        },
        onResize: e => {
          const { componentInstance } = this;
          if (componentInstance) {
            const dialogResize = componentInstance.$options?.dialogResize;
            dialogResize && dialogResize.call(componentInstance, e);
            // this.proxyHandleResize();
          }
          /**
           * resize时触发
           * @event resize
           * @param {object} e 事件对象
           */
          this.$emit('resize', e);
        }
      };
    },
    styles() {
      const wrapperRect = this.wrapperRect || {};
      const centerLeft = Math.max(wrapperRect.width - this.width, 0) / 2;
      const centerTop = Math.max(wrapperRect.height - this.realHeight, 0) / 2;
      return {
        width: `${Math.min(this.width, wrapperRect.width)}px`,
        height: this.realHeight ? `${Math.min(this.realHeight, wrapperRect.height)}px` : 'auto',
        left: this.center ? `${centerLeft}px` : `${this.left}px`,
        top: this.center ? `${centerTop}px` : `${this.top}px`,
        zIndex: this.zIndex,
        display: this.currentMode === 'minimized' ? 'none' : 'block'
      };
    },
    wrapperStyle() {
      const wrapperRect = this.containerOffset || { left: 0, top: 0 };
      return {
        zIndex: this.zIndex,
        display: this.currentMode === 'minimized' ? 'none' : 'block',
        left: -wrapperRect.left + 'px',
        top: -wrapperRect.top + 'px'
      };
    },
    classes() {
      return {
        'is-maximized': this.currentMode === 'maximized',
        'is-lighter': !!this.lighter,
        'is-auto-height': this.center && !this.realHeight
      };
    }
  },
  watch: {
    currentMode: {
      immediate: true,
      handler(v, o) {
        this.currentMode = v;
        /**
         * 模式改变时触发
         * @event mode-change
         * @param {string} v 新模式
         * @param {string} o 旧模式
         */
        this.$emit('mode-change', v, o);
        this.$emit(v);
        // this.$nextTick(this.proxyHandleResize);
        this.$nextTick(this.handleSizeChange);
        if (v === 'maximized') {
          document.body.classList.add('x-dialog--maximized');
        } else {
          document.body.classList.remove('x-dialog--maximized');
        }
      }
    }
  },
  methods: {
    getActivatedVxeTableInstance() {
      if (!this.getVxeTable) return null;
      const list = Array.from(this.getVxeTable() || []);
      const el = list.find(n => {
        return !!n.__vue__?.isActivated;
      });
      return el?.__vue__;
    },
    stopPropagationHandler(e) {
      if (this.stopPropagation) {
        e.stopPropagation();
      }
    },
    handleSizeChange() {
      const body = this.$refs.body;
      if (!body) return;
      const rect = body.getBoundingClientRect();
      /**
       * 当弹窗的尺寸发生改变时触发
       * @event size-change
       * @param {object} rect 尺寸对象
       */
      this.$emit('size-change', rect);
      this.handleResize();
      const { componentInstance } = this;
      if (componentInstance) {
        const dialogSizeChange = componentInstance.$options?.dialogSizeChange;
        dialogSizeChange && dialogSizeChange.call(componentInstance, rect);
      }
    },
    /**
     * 关闭窗体
     * @method close
     */
    async close() {
      const { componentInstance, beforeClose } = this;
      if (componentInstance) {
        const beforeCloseDialog = componentInstance.$options?.beforeCloseDialog;
        if (beforeCloseDialog && await beforeCloseDialog.call(componentInstance, this) === false) {
          return;
        }
        const onCloseDialog = componentInstance.$options?.onCloseDialog;
        onCloseDialog && onCloseDialog.call(componentInstance);
      }
      if (beforeClose) {
        const noop = function () {
        };
        const result = await beforeClose(noop);
        if (result === false) {
          return;
        }
      }
      /**
       * 关闭时触发
       * @event close
       */
      this.$emit('close', this);
      this.$destroy();
    },
    /**
     * 关闭窗体，close 别名
     */
    hide() {
      this.close();
    },
    /**
     * 设置窗体模式
     * @method setMode
     * @param {string} mode, 可选值：popup、maximized、minimized
     */
    setMode(mode) {
      this.currentMode = mode;
    },
    togglePopup() {
      this.currentMode = this.currentMode === 'popup' ? 'maximized' : 'popup';
    },
    setTopLevel() {
      Z_INDEX = Math.max(Z_INDEX, this.zIndex);
      this.zIndex = ++Z_INDEX;
    },
    setWrapperRect() {
      this.wrapperRect = document.body.getBoundingClientRect();
    },
    setComponentInstance() {
      const { componentInstance } = this;
      const el = componentInstance?.$el;
      if (el && this.$refs.body) {
        this.$refs.body.appendChild(el);
        const onOpenDialog = componentInstance.$options?.onOpenDialog;
        onOpenDialog && onOpenDialog.call(componentInstance);
      }
    },
    updateRange({ width = 0, height = 0 }) {
      const length = 50;
      const rect = this.wrapperRect || {};
      const isAutoHeight = this.center && !this.realHeight;
      const diffTop = isAutoHeight ? height / 2 : 0;
      this.dragRange = {
        left: -width + length,
        top: diffTop,
        width: rect.width + width - length,
        height: rect.height + height + diffTop - length
      };
    },
    handleToolClick(item) {
      /**
       * 点击工具按钮时触发
       * @event tool-click
       * @param {object} item 按钮配置对象
       */
      this.$emit('tool-click', item);
    },
    handleClickModal() {
      if (this.closeOnClickModal) {
        this.close();
      }
    },
    handleResize() {
      this.$root.$emit(ROOT_DIALOG_RESIZE_EVENT_NAME, this);
    },
    updateHeight(e) {
      if (this.realHeight) return;
      this.realHeight = this.$refs.dialog.getBoundingClientRect().height;
    },
    handleMouseDown() {
      this.setTopLevel();
      this.updateHeight();
    }
  },
  created() {
    // this.proxyHandleResize = debounce(this.handleResize, 100);
    this.setWrapperRect();
    this.updateRange(this);
    addResizeListener(document.body, this.setWrapperRect);
    if (this.name && this.$admin) {
      this.$admin.dialogInstances[this.name] = this;
    }
    this.activatedVxeTable = this.getActivatedVxeTableInstance();
    if (this.activatedVxeTable) {
      this.activatedVxeTable.blur();
    }
  },
  mounted() {
    /**
     * 窗体显示时触发
     * @event open
     */
    this.$emit('open', this);
    if (this.appendToBody) {
      document.body.appendChild(this.$el);
    } else {
      this.containerOffset = this.$el.getBoundingClientRect();
    }
    document.documentElement.classList.add(`x-dialog-body-${this._uid}`);
    this.setComponentInstance();
    if (this.closeOnPressEscape) {
      this.keyboard = new Keyboard({
        el: this.$el
      });
      this.keyboard.on('Escape', this.close);
    }
    this.$el.focus();
    /**
     * 窗体显示时触发
     * @event opened
     */
    this.$emit('opened', this);
  },
  beforeDestroy() {
    const el = this.componentInstance?.$el;
    if (el && this.$refs.body) {
      el.parentNode?.removeChild(el);
    }
    this.keyboard && this.keyboard.destroy();
    removeResizeListener(document.body, this.setWrapperRect);
    if (this.appendToBody) {
      this.containerOffset = null;
      this.$el.parentNode?.removeChild(this.$el);
    }
    document.documentElement.classList.remove(`x-dialog-body-${this._uid}`);
    this.$el = null;
    if (this.name && this.$admin && this.$admin.dialogInstances) {
      delete this.$admin.dialogInstances[this.name];
    }
    if (this.activatedVxeTable) {
      this.activatedVxeTable.focus();
    }

    this.$emit('closed', this);
    /**
     * 关闭时触发
     * @event close
     */
    this.$emit('destroy', this);
  }
};

</script>
