<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, type Component, type CSSProperties } from "vue";
import { DownOutlined } from "@ant-design/icons-vue";

type EditorActionMenuItem = {
  key: string;
  label: string;
  icon?: Component;
  danger?: boolean;
  disabled?: boolean;
  active?: boolean;
  children?: EditorActionMenuItem[];
};

type EditorActionMenuGroup = {
  key: string;
  columns?: number;
  items: EditorActionMenuItem[];
};

const props = defineProps<{
  x: number;
  y: number;
  groups: EditorActionMenuGroup[];
  variant?: "tile" | "inline";
  minWidth?: string;
  maxWidth?: string;
}>();

const emit = defineEmits<{
  select: [key: string];
}>();

const menuRef = ref<HTMLElement | null>(null);
const frame = ref({
  x: props.x,
  y: props.y,
});

const resolvedVariant = computed(() => props.variant ?? "inline");

const menuStyle = computed<CSSProperties>(() => {
  const style: CSSProperties = {
    left: `${frame.value.x}px`,
    top: `${frame.value.y}px`,
  };

  if (props.minWidth) {
    style.minWidth = props.minWidth;
  }

  if (props.maxWidth) {
    style.maxWidth = props.maxWidth;
  }

  return style;
});

function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

function syncMenuFrame(): void {
  void nextTick(() => {
    const menu = menuRef.value;
    if (!menu) {
      return;
    }

    const offset = 16;
    frame.value = {
      x: clamp(props.x, offset, Math.max(offset, window.innerWidth - menu.offsetWidth - offset)),
      y: clamp(props.y, offset, Math.max(offset, window.innerHeight - menu.offsetHeight - offset)),
    };
  });
}

function handleWindowResize(): void {
  syncMenuFrame();
}

function handleSelect(item: EditorActionMenuItem): void {
  if (item.disabled) {
    return;
  }

  emit("select", item.key);
}

function resolveGroupStyle(group: EditorActionMenuGroup): CSSProperties {
  return {
    gridTemplateColumns: `repeat(${Math.max(group.columns ?? group.items.length, 1)}, minmax(0, 1fr))`,
  };
}

watch(
  () => ({
    x: props.x,
    y: props.y,
    groups: props.groups,
    minWidth: props.minWidth,
    maxWidth: props.maxWidth,
    variant: resolvedVariant.value,
  }),
  () => {
    syncMenuFrame();
  },
  {
    immediate: true,
    deep: true,
    flush: "post",
  },
);

onMounted(() => {
  window.addEventListener("resize", handleWindowResize);
  syncMenuFrame();
});

onBeforeUnmount(() => {
  window.removeEventListener("resize", handleWindowResize);
});
</script>

<template>
  <Teleport to="body">
    <div
      ref="menuRef"
      class="editor-action-menu"
      :class="`editor-action-menu--${resolvedVariant}`"
      :style="menuStyle"
      @pointerdown.stop
      @contextmenu.prevent
    >
      <template v-for="(group, groupIndex) in groups" :key="group.key">
        <div class="editor-action-menu__group" :style="resolveGroupStyle(group)">
          <template v-for="item in group.items" :key="item.key">
            <a-dropdown
              v-if="item.children && item.children.length > 0"
              :trigger="['click']"
              overlay-class-name="editor-action-menu__dropdown-overlay"
              :overlay-style="{ zIndex: 2000 }"
            >
              <button
                type="button"
                class="editor-action-menu__action"
                :class="{
                  'editor-action-menu__action--active': item.active,
                  'editor-action-menu__action--danger': item.danger,
                }"
                :disabled="item.disabled"
              >
                <component :is="item.icon" v-if="item.icon" />
                <span v-if="resolvedVariant === 'tile'">{{ item.label }}</span>
                <DownOutlined class="editor-action-menu__dropdown-icon" />
              </button>
              <template #overlay>
                <a-menu>
                  <a-menu-item
                    v-for="child in item.children"
                    :key="child.key"
                    :disabled="child.disabled"
                    @click="handleSelect(child)"
                  >
                    <span
                      class="editor-action-menu__dropdown-item"
                      :class="{ 'editor-action-menu__dropdown-item--danger': child.danger }"
                    >
                      <component :is="child.icon" v-if="child.icon" />
                      <span>{{ child.label }}</span>
                    </span>
                  </a-menu-item>
                </a-menu>
              </template>
            </a-dropdown>
            <button
              v-else
              type="button"
              class="editor-action-menu__action"
              :class="{
                'editor-action-menu__action--active': item.active,
                'editor-action-menu__action--danger': item.danger,
              }"
              :disabled="item.disabled"
              @click="handleSelect(item)"
            >
              <component :is="item.icon" v-if="item.icon" />
              <span v-if="resolvedVariant === 'tile'">{{ item.label }}</span>
            </button>
          </template>
        </div>

        <div v-if="groupIndex < groups.length - 1" class="editor-action-menu__divider"></div>
      </template>
    </div>
  </Teleport>
</template>

<style scoped>
.editor-action-menu {
  position: fixed;
  z-index: 1200;
  display: flex;
  flex-direction: column;
}

.editor-action-menu--tile {
  gap: 16px;
  padding: 16px;
  border: 1px solid #edf2fa;
  border-radius: 16px;
  background: #ffffff;
  box-shadow:
    0 20px 48px rgba(15, 23, 42, 0.08),
    0 6px 16px rgba(15, 23, 42, 0.04);
}

.editor-action-menu--inline {
  gap: 10px;
  padding: 12px;
  border: 1px solid #e8eef8;
  border-radius: 16px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 251, 255, 0.98) 100%);
  box-shadow:
    0 20px 48px rgba(15, 23, 42, 0.1),
    0 6px 18px rgba(15, 23, 42, 0.06);
  backdrop-filter: blur(14px);
}

.editor-action-menu__group {
  display: grid;
  gap: 12px;
}

.editor-action-menu--inline .editor-action-menu__group {
  gap: 10px;
}

.editor-action-menu__divider {
  height: 1px;
  background: #f0f3f8;
}

.editor-action-menu__action {
  border: 1px solid transparent;
  cursor: pointer;
  transition: all 0.2s ease;
}

.editor-action-menu__action span {
  line-height: 1.4;
}

.editor-action-menu__action :deep(svg) {
  transition: color 0.2s ease;
}

.editor-action-menu--tile .editor-action-menu__action {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 86px;
  padding: 12px 10px;
  border-radius: 12px;
  background: #f7f9fa;
  color: #3f4a5c;
}

.editor-action-menu--tile .editor-action-menu__action span {
  font-size: 13px;
  font-weight: 600;
  text-align: center;
}

.editor-action-menu--tile .editor-action-menu__action :deep(svg) {
  font-size: 22px;
  color: #5c6b81;
}

.editor-action-menu--tile .editor-action-menu__action:hover:not(:disabled) {
  background: #eff4ff;
  border-color: #c2d6ff;
  color: #245bdb;
}

.editor-action-menu--tile .editor-action-menu__action:hover:not(:disabled) :deep(svg) {
  color: #245bdb;
}

.editor-action-menu--inline .editor-action-menu__action {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 36px;
  padding: 0 14px;
  border-radius: 12px;
  background: #f7f9fc;
  color: #344054;
  font-size: 13px;
  font-weight: 600;
}

.editor-action-menu--inline .editor-action-menu__action :deep(svg) {
  font-size: 18px;
  color: #52607a;
}

.editor-action-menu--inline .editor-action-menu__action:hover:not(:disabled),
.editor-action-menu--inline .editor-action-menu__action--active {
  border-color: #c8d8ff;
  background: #edf4ff;
  color: #245bdb;
  transform: translateY(-1px);
}

.editor-action-menu--inline .editor-action-menu__action:hover:not(:disabled) :deep(svg),
.editor-action-menu--inline .editor-action-menu__action--active :deep(svg) {
  color: #245bdb;
}

.editor-action-menu__action--danger:hover:not(:disabled) {
  border-color: #ffd2cf;
  background: #fff1f0;
  color: #cf1322;
}

.editor-action-menu__action--danger:hover:not(:disabled) :deep(svg) {
  color: #cf1322;
}

.editor-action-menu--tile .editor-action-menu__action:disabled {
  opacity: 0.46;
}

.editor-action-menu__action:disabled {
  cursor: not-allowed;
}
.editor-action-menu__dropdown-icon {
  font-size: 10px !important;
  color: #8c9eb5 !important;
  transition: transform 0.2s;
}

.editor-action-menu__dropdown-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 500;
  color: #3f4a5c;
}

.editor-action-menu__dropdown-item--danger {
  color: #cf1322;
}

.editor-action-menu__dropdown-item--danger :deep(svg) {
  color: #cf1322;
}

:global(.editor-action-menu__dropdown-overlay) {
  z-index: 2000;
  margin-top: 10px;
}
</style>
