<template>
  <div
    ref="dropdownRef"
    :style="`--max-height: ${getMaxHeight}`"
    :class="[
      {'--top': top || autoTop},
      {'--right': right},
      {'--center': center},
      {'--open': open},
      {'--separate': separateLastChild},
    ]"
    class="ui-dropdown"
    @mouseleave="mouseHandler(false)"
    @mouseenter="mouseHandler(true)"
  >
    <div
      class="ui-dropdown__trigger"
      @click="openDropdown"
    >
      <slot name="trigger"/>
    </div>
    <transition
      name="ui-dropdown"
      mode="out-in"
    >
      <dialog
        v-if="useVIf ? open && $slots.items : true"
        ref="menuRef"
        :style="!useVIf ? { display: (open && $slots.items) ? '' : 'none' } : {}"
        :class="{'--open': open}"
        class="ui-dropdown__menu"
        @click="close"
        @mouseenter="mouseHandler(true)"
        @mouseleave="mouseHandler(false)"
      >
        <button
          :aria-label="$t('close')"
          class="ui-dropdown__crest _to-md"
          type="button"
          @click="close"
        >
          <UiIcon
            name="close"
            size="20"
          />
        </button>
        <slot name="header"/>
        <div
          v-if="$slots.items"
          class="ui-dropdown__menu-items"
        >
          <slot
            name="items"
            v-bind="'ui-dropdown__item'"
          />
        </div>
      </dialog>
    </transition>
  </div>
</template>

<script setup lang="ts">
// На компоненте обязательно вызывать пропсы слота и вешать слот пропс (className) на непосредственных детей
import { checkDeviceWidth } from '../../runtime';
import { CSS_BREAKPOINTS } from '../../config/css-breakpoints';
import { onClickOutside } from '@vueuse/core';

const dropdownRef = ref();
const menuRef = ref();

const props = withDefaults(defineProps<{
  top?: boolean;
  right?: boolean;
  center?: boolean;
  separateLastChild?: boolean; // deprecated, вешать на нужный child класс модификатор --separate
  maxHeight?: string | number;
  openByHover?: boolean;
  useVIf?: boolean;
}>(), {
  useVIf: false
});

const open = ref(false);
const autoTop = ref(false);

onClickOutside(dropdownRef, () => {
  open.value = false;
});

const { scrollLock, scrollUnlock } = useGlobalScrollLock();

const { registerOverlay, releaseOverlay } = useOverlayStack();

const breakpoints = CSS_BREAKPOINTS;
const { isTablet } = checkDeviceWidth(breakpoints);

function checkPosition() {
  if (!dropdownRef.value || !menuRef.value || isTablet.value) return;
  requestAnimationFrame(() => {
    const triggerRect = dropdownRef.value.getBoundingClientRect();
    const menuHeight = menuRef.value.scrollHeight;
    const spaceBelow = window.innerHeight - triggerRect.bottom;
    autoTop.value = menuHeight > spaceBelow;
  });
}

function openDropdown() {
  open.value = !open.value;
  if (open.value) nextTick(() => checkPosition());

  if (isTablet.value && open.value) {
    nextTick(() => {
      if (menuRef.value?.showModal) {
        menuRef.value.showModal();
      }
    });
  }
}

function close() {
  open.value = false;

  if (isTablet.value) {
    nextTick(() => {
      menuRef.value?.close?.();
    });
  }
}

watch(open, (val) => {
  if (val) {
    if (!isTablet.value) return;
    scrollLock();
    // На мобиле меню — модальный шит с анимацией, поэтому он тоже участник стека
    // оверлеев: попап, открытый из него, не должен появляться с анимацией.
    nextTick(() => registerOverlay(menuRef.value));
  } else {
    if (isTablet.value) scrollUnlock();
    releaseOverlay(menuRef.value);
  }
});

onBeforeUnmount(() => {
  if (open.value && isTablet.value) {
    scrollUnlock();
    releaseOverlay(menuRef.value);
  }
});

let hoverTimeout: ReturnType<typeof setTimeout> | null = null;

function mouseHandler(event: boolean) {
  if (!isTablet.value && props.openByHover) {
    if (hoverTimeout) clearTimeout(hoverTimeout);

    if (event) {
      open.value = true;
      nextTick(() => checkPosition());
    } else {
      hoverTimeout = setTimeout(() => {
        open.value = false;
      }, 100);
    }
  }
}

const getMaxHeight = computed(() => {
  return props.maxHeight ? `${props.maxHeight}px` : 'none';
});

defineExpose({ openDropdown, open });
</script>

<style lang="scss">
.ui-dropdown {
  position: relative;
}

.ui-dropdown__menu {
  @include to-br(md) {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    top: auto;
    margin: 0;
    width: 100vw;
    background-color: $dropdown-mobile-bg;
    border-top: 1px solid $dropdown-mobile-border;
    border-radius: $radius-2xl $radius-2xl 0 0;
  }

  @include from-br(md) {
    position: absolute;
    bottom: -4px;
    z-index: $z-index-dropdown;
    margin: 0;
    opacity: 1;
    max-height: none;
    max-width: none;
    background-color: $dropdown-menu-bg;
    border: 1px solid $dropdown-menu-border;
    border-radius: $radius;
    transform: translateY(100%);
    user-select: none;
    overflow: hidden;

    &::after {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      z-index: $z-index-plus;
      height: 20px;
      width: 100%;
      transform: translateY(-50%);
      cursor: pointer;
    }
  }
}

.ui-dropdown__menu-items {
  overflow: auto;

  @include to-br(md) {
    display: grid;
    width: 100%;
    max-height: 80vh;
    padding-bottom: var(--popup-padding-bottom-mobile);
  }

  @include from-br(md) {
    max-height: var(--max-height);
    display: grid;
    align-items: start;
    align-content: start;
    min-width: 158px;
  }
}

.ui-dropdown.--top .ui-dropdown__menu::after {
  @include from-br(md) {
    bottom: 0;
    top: auto;
    transform: translateY(50%);
  }
}

.--open.ui-dropdown .ui-dropdown__menu::after {
  @include from-br(md) {
    content: '';
  }
}

.ui-dropdown.--top .ui-dropdown__menu {
  @include from-br(md) {
    bottom: auto;
    top: -10px;
    transform: translateY(-100%);
  }
}

.ui-dropdown.--right .ui-dropdown__menu {
  @include from-br(md) {
    right: 0;
    left: auto;
  }
}

.ui-dropdown.--center .ui-dropdown__menu {
  @include from-br(md) {
    left: 50%;
    transform: translateX(-50%);
  }
}

.ui-dropdown.--center.--top .ui-dropdown__menu {
  @include from-br(md) {
    transform: translate(-50%, -100%);
  }
}

.ui-dropdown__menu-items > .ui-dropdown__item {
  @include font-md;

  display: flex;
  gap: 16px;
  padding-block: 16px;
  padding-inline: 20px;
  white-space: nowrap;
  align-items: center;
  color: $dropdown-mobile-item-text;
  min-width: max-content;
  width: 100%;
  overflow: hidden;

  @include from-br(md) {
    @include font-md;

    padding-block: 12px;
    padding-inline: 16px;
    gap: 12px;
    color: $dropdown-menu-text;
  }

  @include hover(background-color) {
    background-color: $dropdown-menu-bg--hover;
  }
}

.ui-dropdown__item:not(:last-child) {
  @include from-br(md) {
    border-bottom: 1px solid $dropdown-menu-border;
  }
}

.ui-dropdown__menu-items > .ui-dropdown__item .ui-icon {
  color: $dropdown-mobile-icon;

  @include from-br(md) {
    color: $dropdown-icon;
  }
}

.ui-dropdown__menu::backdrop {
  @include to-br(md) {
    backdrop-filter: blur($blur-sm);
    background-color: $popup-decorate-bg;
  }
}

.ui-dropdown .ui-dropdown__item.--separate,
.ui-dropdown.--separate .ui-dropdown__menu-items > .ui-dropdown__item:last-child {
  @include from-br(md) {
    position: relative;
    margin-top: 4px;

    &::after {
      content: '';
      position: absolute;
      top: -4px;
      left: 0;
      transform: translateY(-50%);
      width: 100%;
      height: 1px;
      background-color: $dropdown-separator;
    }
  }
}

.ui-dropdown__trigger {
  display: flex;
  column-gap: 8px;
  align-items: center;
}

.ui-dropdown__crest {
  @include to-br(md) {
    position: absolute;
    top: 16px;
    right: 16px;
    display: flex;
    color: $dropdown-crest;

    &::after {
      @include area-point;
    }
  }
}

.ui-dropdown__menu.--open:has(.ui-dropdown__menu-items) {
  @include from-br(md) {
    display: block !important;
  }
}

.ui-dropdown:not(.--open) .ui-dropdown__menu {
  @include from-br(md) {
    pointer-events: none;
  }
}

/* Гейты `--no-*-anim` вешает useOverlayStack: шит не анимируется, если он открывается
   поверх попапа или закрывается, отдавая место другому оверлею. */
@include to-br(md) {
  .ui-dropdown__menu:where(:not(.--no-enter-anim)).ui-dropdown-enter-active,
  .ui-dropdown__menu:where(:not(.--no-leave-anim)).ui-dropdown-leave-active {
    transition: transform 300ms ease;
  }

  .ui-dropdown__menu:where(:not(.--no-enter-anim)).ui-dropdown-enter-from {
    transform: translateY(100%);
  }

  .ui-dropdown__menu:where(:not(.--no-enter-anim)).ui-dropdown-enter-to {
    transform: translateY(0);
  }

  .ui-dropdown__menu:where(:not(.--no-leave-anim)).ui-dropdown-leave-from {
    transform: translateY(0);
  }

  .ui-dropdown__menu:where(:not(.--no-leave-anim)).ui-dropdown-leave-to {
    transform: translateY(100%);
  }
}

@include from-br(md) {
  .ui-dropdown-enter-active,
  .ui-dropdown-leave-active {
    transition: 100ms linear opacity;
    transition-delay: 100ms;
  }

  .ui-dropdown-enter-from,
  .ui-dropdown-leave-to {
    opacity: 0;
  }
}
</style>
