<template>
  <div class="context-menu" @click="visible = false">
    <a-dropdown
      :trigger="['contextmenu']"
      :overlayClassName="`context-menu-overlay ${randomClass}`"
      v-model:visible="visible"
      ref="dropdownRef"
    >
      <div class="trigger-area" ref="triggerAreaRef">
        <slot></slot>
      </div>
      <template #overlay v-if="!disabled">
        <a-menu>
          <context-sub-menu @hide="visible = false" :menu-groups="menuGroups" />
        </a-menu>
      </template>
    </a-dropdown>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, PropType, onMounted, watchEffect } from 'vue';
import { Dropdown, Menu } from 'ant-design-vue';
import { isFunction } from 'lodash';
import { on, query } from '@/utils/frontend/dom';
import ContextSubMenu from './ContextSubMenu.vue';

/*
对于每个菜单项的 text，我们将其拆解成一个个的 widget
如：菜单项1-1{%input[type=text][value=张三]%}行
*/
interface IWidget {
  // 普通文本或者输入框
  component: 'text' | 'input';
  // 文本值或者输入框(文本输入框或者数字输入框)的值
  value?: string | number;
  // 输入框的类型
  type?: 'text' | 'number';
  // 数字输入框最小值
  min?: number;
  // 数字输入框最大值
  max?: number;
  // 数字输入框步数
  step?: number;
  // 精度
  precision?: number;
  // 百分比
  percent?: boolean;
}

interface IMenuItem extends ContextMenu.IMenuItem {
  content: IWidget[];
  submenu?: IMenuItem[][];
}

// 菜单项的每一项
export type MenuItem = IMenuItem;
// 菜单项的每一个 group
export type MenuGroup = IMenuItem[];

export default defineComponent({
  name: 'ContextMenu',
  components: { ContextSubMenu, ADropdown: Dropdown, AMenu: Menu },
  props: {
    // 菜单分组列表
    itemGroups: {
      type: Array as PropType<MenuGroup[]>,
      required: true,
    },
    // 是否禁用上下文菜单（不显示上下文菜单）
    disabled: {
      type: Boolean,
      default: false,
    },
    // 全局只读（会显示右键菜单，但是全部子项为 disabled）
    readOnly: {
      type: Boolean,
      default: false,
    },
    // 触发区域是否根据子元素的大小而定
    autoSize: {
      type: Boolean,
      default: false,
    },
  },
  setup(props) {
    // 用户传过来的 text 中可能含有包括 input 输入框的格式符，通过此函数可以将它们拆解出来，变成一个个的 widget
    const getWidgets = (text: string) => {
      const inputReg = /({%input.*?%})/;
      const splits: string[] = text.split(inputReg);
      const widgets: IWidget[] = [];
      splits.forEach(item => {
        if (inputReg.test(item)) {
          // 匹配 input 模式字符串
          const attrs = item.match(
            /(\[type=(text|number)\])|(\[value=.*?\])|(\[min=.*?\])|(\[max=.*?\])|(\[step=.*?\])|(\[percent=.*?\])|(\[precision=.*?\])/g
          );
          const metaArr: Common.IStringIndex[] = [];
          if (attrs) {
            attrs.forEach(attr => {
              const [key, val] = attr.replace('[', '').replace(']', '').split('=');
              metaArr.push({
                [key]: val,
              });
            });
          }
          let widget: IWidget = { component: 'input' };
          metaArr.forEach((meta: any) => {
            widget = Object.assign(widget, { ...meta });
          });
          if (widget.type === 'number') {
            widget.min = widget.min ? parseInt(`${widget.min}`, 10) : 1;
            widget.max = widget.max ? parseInt(`${widget.max}`, 10) : 100;
            widget.step = widget.step ? widget.step : 1;
            widget.precision = widget.precision ? parseInt(`${widget.precision}`, 10) : 0;
            widget.percent = `${widget.percent}` === 'true';
            widget.value = widget.value ? parseFloat(`${widget.value}`) : widget.min;
          } else if (widget.type === 'text') {
            widget.value = widget.value ? widget.value : '';
          }
          widgets.push(widget);
        } else if (item.trim() !== '') {
          // 普通文本字符串
          widgets.push({
            component: 'text',
            value: item,
          });
        }
      });
      return widgets;
    };

    // 递归求解父组件传过来的菜单分组数据
    const reshapeGroups = async (groups: MenuGroup[]) => {
      const menuGroups: MenuGroup[] = [];
      for (const group of groups) {
        const menuGroup: MenuGroup = [];
        for (const item of group) {
          const menuItem: MenuItem = {
            text: isFunction(item.text) ? item.text() : item.text,
            content: getWidgets(isFunction(item.text) ? item.text() : item.text),
            icon: item.icon,
            hidden: isFunction(item.hidden) ? item.hidden() : item.hidden,
            // 全局只读优先级最高
            // eslint-disable-next-line no-nested-ternary
            disable: props.readOnly === true ? true : isFunction(item.disable) ? await item.disable() : item.disable,
            callback: item.callback,
            submenu: item.submenu ? await reshapeGroups(item.submenu) : undefined,
          };
          menuGroup.push(menuItem);
        }
        menuGroups.push(menuGroup);
      }

      return menuGroups;
    };

    // 菜单项数组
    const menuGroups: any = ref([]);

    // 菜单是否可见
    const visible = ref(false);

    // 每次右键的时候，都重新计算一下
    const triggerAreaRef = ref<HTMLElement | null>(null);
    onMounted(() => {
      const triggerArea = triggerAreaRef.value;
      if (triggerArea) {
        on(triggerArea, 'contextmenu', async () => {
          menuGroups.value = await reshapeGroups(props.itemGroups);
        });

        if (props.autoSize) {
          triggerArea.style.height = 'auto';
        }
      }
    });

    const randomClass = `context-menu-overlay-${Math.random().toString().slice(2)}`;

    // 禁用当前 dropdown menu 自身的系统右键上下文菜单
    watchEffect(() => {
      if (!visible.value) {
        return;
      }
      setTimeout(() => {
        const contextMenuOverlay = query(document.body, `.${randomClass}`) as HTMLElement;
        if (contextMenuOverlay) {
          contextMenuOverlay.oncontextmenu = (e: MouseEvent) => e.preventDefault();
        }
      }, 200);
    });

    // 手动隐藏，供外界调用
    const Hide = () => {
      visible.value = false;
    };

    return {
      menuGroups,
      visible,
      randomClass,
      triggerAreaRef,
      Hide,
    };
  },
});
</script>

<style lang="scss" scoped>
.trigger-area {
  position: relative;
  width: 100%;
  height: 100%;
}

.context-menu {
  width: 100%;
  height: 100%;
}
</style>
<style lang="scss">
.context-menu-overlay {
  @include dropdown-menu(220px, auto, 190px);
}
</style>
