<template>
  <div
    ref="refs"
    class="menu-overlay"
    :style="{ width, height }"
    @mouseover="handleMouseover"
    @mouseout="handleMouseout"
    @click="handleClick"
  >
    <slot />
  </div>
  <teleport to="body">
    <div
      ref="panelRef"
      class="menu-overlay-panel"
      :class="{ 'menu-overlay-panel-enter': open, 'menu-overlay-panel-exit': !open }"
      :style="stl"
      @mouseover="handleMouseover"
      @mouseout="handleMouseout"
    >
      <slot name="overlay" />
    </div>
  </teleport>
</template>

<script lang="ts" setup>
import type { CSSProperties } from 'vue';
import { computed, ref, watch } from 'vue';

const props = withDefaults(
  defineProps<{
    open?: boolean;
    trigger?: 'hover' | 'click' | null;
    align?: 'left' | 'center' | 'right';
    width?: string;
    height?: string;
    cover?: boolean;
    offsetX?: number;
    offsetY?: number;
    overlayStyle?: CSSProperties;
  }>(),
  {
    open: false,
    trigger: 'hover',
    width: 'auto',
    height: '46px',
    cover: true,
    offsetX: 0,
    offsetY: 0,
    align: 'left'
  }
);

const emits = defineEmits<{
  (e: 'update:open', value: boolean): void;
}>();

const open = ref(props.open);
const refs = ref<HTMLDivElement>();
const panelRef = ref<HTMLDivElement>();
let timer: any = null;

const handleMouseover = () => {
  if (props.trigger !== 'hover') return;
  clearTimeout(timer);
  timer = null;
  open.value = true;
  emits('update:open', true);
};
const handleMouseout = () => {
  if (props.trigger !== 'hover') return;
  clearTimeout(timer);
  timer = null;
  timer = setTimeout(() => {
    open.value = false;
    emits('update:open', false);
  }, 200);
};

const handleClick = () => {
  if (props.trigger !== 'click') return;
  open.value = !open.value;
  emits('update:open', open.value);
};

const handleClickOutside = (e: MouseEvent) => {
  const target = e.target as HTMLElement;
  if (!refs.value?.contains(target) && !panelRef.value?.contains(target)) {
    open.value = false;
    emits('update:open', false);
  }
};

const stl = computed(() => {
  const { x, y } = refs.value?.getBoundingClientRect() || { x: 0, y: 0 };
  const width = refs.value?.clientWidth || 0;
  const height = refs.value?.clientHeight || 0;
  let left = x;
  if (props.align === 'center') {
    left = x + width / 2;
  } else if (props.align === 'right') {
    const panelWidth = panelRef.value?.clientWidth || 0;
    left = x + (width - panelWidth);
  }
  if (props.cover) {
    left = x;
  }
  return {
    top: `${y + height + props.offsetY}px`,
    width: props.cover ? `${width}px` : 'auto',
    left: `${left + props.offsetX}px`,
    ...(props.overlayStyle || {})
  };
});

watch(
  () => props.open,
  () => (open.value = props.open)
);
watch(
  () => open.value,
  () => {
    if (open.value) {
      document.addEventListener('click', handleClickOutside);
    } else {
      document.removeEventListener('click', handleClickOutside);
    }
  }
);
</script>

<style scoped>
.menu-overlay {
  position: relative;
  width: auto;
  min-width: 52px;
  height: 46px;
}
.menu-overlay-panel {
  position: fixed;
  z-index: 10000;
  width: auto;
  max-width: 100vw;
  height: auto;
  max-height: 100vh;
  transition: background-color 0.3s;
  pointer-events: none;
  opacity: 0;
  transform-origin: top center;
  animation: zoomOut 0.3s;
}

.menu-overlay-panel-enter {
  pointer-events: auto;
  opacity: 100%;
  animation: zoomIn 0.3s;
}

.menu-overlay-panel-exit {
  background-color: transparent !important;
}

@keyframes zoomOut {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: scale3d(0.98, 0.98, 0.98);
  }
}
@keyframes zoomIn {
  0% {
    opacity: 80%;
  }
  100% {
    opacity: 1;
    transform: scale3d(1, 1, 1);
  }
}
</style>
