<script setup lang="ts">
import {
  computed,
  nextTick,
  onBeforeUnmount,
  onMounted,
  ref,
  watch,
} from 'vue';

import { useI18n } from '@vben/locales';

import { UserAvatar } from '#/components/user-avatar';
import { getUserListApi, type User } from '#/api/core/user';

defineOptions({ name: 'MentionDropdown' });

const props = withDefaults(
  defineProps<{
    /** Element to anchor the dropdown positioning */
    anchorEl?: HTMLElement | null;
    /** Maximum dropdown height in px */
    maxHeight?: number;
    /** Preferred placement relative to anchor */
    placement?: 'auto' | 'bottom' | 'top';
    /** Search query (text after @) */
    query: string;
    /** Controls visibility */
    visible: boolean;
  }>(),
  {
    anchorEl: null,
    maxHeight: 300,
    placement: 'auto',
  },
);

const emit = defineEmits<{
  select: [user: User];
  'update:visible': [val: boolean];
}>();

const { t } = useI18n();

const users = ref<User[]>([]);
const loading = ref(false);
const activeIndex = ref(0);
const dropdownRef = ref<HTMLElement | null>(null);
const position = ref({ left: 0, top: 0 });
const resolvedPlacement = ref<'bottom' | 'top'>('top');

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

watch(
  () => props.query,
  (q) => {
    if (!props.visible) return;
    activeIndex.value = 0;
    searchUsers(q);
  },
);

watch(
  () => props.visible,
  (v) => {
    if (v) {
      activeIndex.value = 0;
      searchUsers(props.query);
      nextTick(updatePosition);
    }
  },
);

function searchUsers(query: string) {
  if (searchTimer) clearTimeout(searchTimer);
  loading.value = true;
  searchTimer = setTimeout(async () => {
    try {
      const res = await getUserListApi({
        name: query || undefined,
        page: 1,
        pageSize: 10,
      });
      users.value = res.items || [];
    } catch {
      users.value = [];
    }
    loading.value = false;
    nextTick(updatePosition);
  }, 200);
}

function updatePosition() {
  if (!props.anchorEl || !dropdownRef.value) return;

  const anchorRect = props.anchorEl.getBoundingClientRect();
  const dropdownEl = dropdownRef.value;
  const dropdownHeight = dropdownEl.offsetHeight || props.maxHeight;
  const dropdownWidth = dropdownEl.offsetWidth || 300;
  const vh = window.innerHeight;
  const vw = window.innerWidth;

  let placement = props.placement;
  if (placement === 'auto') {
    const spaceAbove = anchorRect.top;
    const spaceBelow = vh - anchorRect.bottom;
    placement = spaceBelow >= dropdownHeight + 8 ? 'bottom' : 'top';
  }
  resolvedPlacement.value = placement;

  let top: number;
  if (placement === 'bottom') {
    top = anchorRect.bottom + 4;
  } else {
    top = anchorRect.top - dropdownHeight - 4;
  }
  top = Math.max(8, Math.min(top, vh - dropdownHeight - 8));

  let left = anchorRect.left;
  if (left + dropdownWidth > vw - 8) {
    left = vw - dropdownWidth - 8;
  }
  left = Math.max(8, left);

  position.value = { left, top };
}

function handleKeydown(e: KeyboardEvent): boolean {
  if (!props.visible) return false;

  if (loading.value && users.value.length === 0) {
    if (e.key === 'Escape') {
      e.preventDefault();
      emit('update:visible', false);
      return true;
    }
    return false;
  }

  if (users.value.length === 0) {
    if (e.key === 'Escape') {
      e.preventDefault();
      emit('update:visible', false);
      return true;
    }
    return false;
  }

  switch (e.key) {
    case 'ArrowDown': {
      e.preventDefault();
      activeIndex.value = (activeIndex.value + 1) % users.value.length;
      scrollToActive();
      return true;
    }
    case 'ArrowUp': {
      e.preventDefault();
      activeIndex.value =
        (activeIndex.value - 1 + users.value.length) % users.value.length;
      scrollToActive();
      return true;
    }
    case 'Enter': {
      if (!e.shiftKey) {
        e.preventDefault();
        selectUser(users.value[activeIndex.value]!);
        return true;
      }
      return false;
    }
    case 'Tab': {
      e.preventDefault();
      selectUser(users.value[activeIndex.value]!);
      return true;
    }
    case 'Escape': {
      e.preventDefault();
      emit('update:visible', false);
      return true;
    }
    default:
      return false;
  }
}

function scrollToActive() {
  nextTick(() => {
    const listEl = dropdownRef.value?.querySelector('.mention-list');
    const activeItem = listEl?.children[activeIndex.value] as
      | HTMLElement
      | undefined;
    if (activeItem && listEl) {
      activeItem.scrollIntoView({ block: 'nearest' });
    }
  });
}

function selectUser(user: User) {
  emit('select', user);
  emit('update:visible', false);
}

function highlightMatch(text: string, query: string): string {
  if (!query) return escapeHtml(text);
  const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const regex = new RegExp(`(${escaped})`, 'gi');
  return escapeHtml(text).replace(
    regex,
    '<mark class="mention-highlight">$1</mark>',
  );
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

function onViewportChange() {
  if (props.visible) updatePosition();
}

const showFooterHint = computed(() => {
  return props.visible && users.value.length > 0 && !loading.value;
});

onMounted(() => {
  window.addEventListener('resize', onViewportChange);
  window.addEventListener('scroll', onViewportChange, true);
});

onBeforeUnmount(() => {
  window.removeEventListener('resize', onViewportChange);
  window.removeEventListener('scroll', onViewportChange, true);
  if (searchTimer) clearTimeout(searchTimer);
});

defineExpose({ handleKeydown });
</script>

<template>
  <Teleport to="body">
    <Transition name="mention-fade">
      <div
        v-if="visible"
        ref="dropdownRef"
        class="mention-dropdown"
        :class="[`placement-${resolvedPlacement}`]"
        :style="{
          top: `${position.top}px`,
          left: `${position.left}px`,
          maxHeight: `${maxHeight}px`,
        }"
      >
        <!-- Loading skeleton -->
        <div v-if="loading && users.length === 0" class="mention-loading">
          <div v-for="i in 3" :key="i" class="mention-skeleton">
            <div class="skeleton-avatar" />
            <div class="skeleton-info">
              <div class="skeleton-name" />
              <div class="skeleton-dept" />
            </div>
          </div>
        </div>

        <!-- Empty state -->
        <div v-else-if="!loading && users.length === 0" class="mention-empty">
          <div class="mention-empty-icon">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="28"
              height="28"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="1.5"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <circle cx="11" cy="11" r="8" />
              <line x1="21" y1="21" x2="16.65" y2="16.65" />
            </svg>
          </div>
          <span>{{ t('mention.noResults') }}</span>
        </div>

        <!-- User list -->
        <div v-else class="mention-list">
          <div
            v-for="(user, idx) in users"
            :key="user.id"
            class="mention-item"
            :class="{ active: idx === activeIndex }"
            @mousedown.prevent="selectUser(user)"
            @mouseenter="activeIndex = idx"
          >
            <UserAvatar
              :user-id="user.id"
              :name="user.name || user.username"
              :size="32"
              :font-size="13"
              :shadow="false"
              :show-popover="false"
            />
            <div class="mention-user-info">
              <div
                class="mention-user-name"
                v-html="highlightMatch(user.name || user.username || '', query)"
              />
              <div v-if="user.dept_name" class="mention-user-dept">
                {{ user.dept_name }}
              </div>
            </div>
          </div>
        </div>

        <!-- Footer keyboard hints -->
        <div v-if="showFooterHint" class="mention-hint">
          <span
            ><kbd>&uarr;</kbd><kbd>&darr;</kbd>
            {{ t('mention.navigate') }}</span
          >
          <span><kbd>Enter</kbd> {{ t('mention.select') }}</span>
          <span><kbd>Esc</kbd> {{ t('mention.close') }}</span>
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<style lang="scss" scoped>
.mention-dropdown {
  position: fixed;
  z-index: 10000;
  display: flex;
  flex-direction: column;
  width: 300px;
  overflow: hidden;
  background: var(--zq-bg-primary, #fff);
  border: 1px solid var(--zq-border-color-light, #e4e7ed);
  border-radius: 12px;
  box-shadow:
    0 8px 24px rgba(0, 0, 0, 0.1),
    0 2px 8px rgba(0, 0, 0, 0.06);
}

.mention-list {
  max-height: 260px;
  padding: 4px;
  overflow-y: auto;

  &::-webkit-scrollbar {
    width: 4px;
  }

  &::-webkit-scrollbar-thumb {
    background: var(--zq-border-color, #dcdfe6);
    border-radius: 2px;
  }

  &::-webkit-scrollbar-track {
    background: transparent;
  }
}

.mention-item {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 8px 10px;
  cursor: pointer;
  border-radius: 8px;
  transition: background 0.12s;

  &.active {
    background: var(--el-color-primary-light-9, #ecf5ff);
  }

  &:not(.active):hover {
    background: var(--zq-bg-cell-hover, rgba(0, 0, 0, 0.03));
  }
}

.mention-user-info {
  flex: 1;
  min-width: 0;
}

.mention-user-name {
  overflow: hidden;
  font-size: 13px;
  font-weight: 500;
  color: var(--zq-text-primary);
  text-overflow: ellipsis;
  white-space: nowrap;

  :deep(.mention-highlight) {
    font-weight: 600;
    color: var(--el-color-primary, #409eff);
    background: transparent;
  }
}

.mention-user-dept {
  margin-top: 1px;
  overflow: hidden;
  font-size: 11px;
  color: var(--zq-text-placeholder);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Loading skeleton */
.mention-loading {
  padding: 4px;
}

.mention-skeleton {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 8px 10px;

  .skeleton-avatar {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    background: linear-gradient(
      90deg,
      var(--el-fill-color, #f0f2f5) 25%,
      var(--el-fill-color-light, #f5f7fa) 50%,
      var(--el-fill-color, #f0f2f5) 75%
    );
    background-size: 200% 100%;
    border-radius: 50%;
    animation: mention-shimmer 1.5s infinite;
  }

  .skeleton-info {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 5px;
  }

  .skeleton-name,
  .skeleton-dept {
    border-radius: 4px;
    background: linear-gradient(
      90deg,
      var(--el-fill-color, #f0f2f5) 25%,
      var(--el-fill-color-light, #f5f7fa) 50%,
      var(--el-fill-color, #f0f2f5) 75%
    );
    background-size: 200% 100%;
    animation: mention-shimmer 1.5s infinite;
  }

  .skeleton-name {
    width: 55%;
    height: 14px;
  }

  .skeleton-dept {
    width: 35%;
    height: 10px;
  }
}

/* Empty state */
.mention-empty {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
  padding: 24px 16px;
  font-size: 12px;
  color: var(--zq-text-placeholder);
}

.mention-empty-icon {
  opacity: 0.35;
}

/* Footer hints */
.mention-hint {
  display: flex;
  gap: 14px;
  padding: 5px 12px;
  font-size: 10px;
  color: var(--zq-text-placeholder);
  background: var(--zq-bg-secondary, #f5f7fa);
  border-top: 1px solid var(--zq-border-color-light, #e4e7ed);

  span {
    display: flex;
    gap: 3px;
    align-items: center;
  }

  kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 3px;
    font-family: inherit;
    font-size: 9px;
    line-height: 1;
    color: var(--zq-text-secondary);
    background: var(--zq-bg-primary, #fff);
    border: 1px solid var(--zq-border-color, #dcdfe6);
    border-radius: 3px;
  }
}

/* Transitions */
.mention-fade-enter-active,
.mention-fade-leave-active {
  transition:
    opacity 0.15s ease,
    transform 0.15s ease;
}

.mention-fade-enter-from,
.mention-fade-leave-to {
  opacity: 0;
}

.placement-top {
  .mention-fade-enter-from,
  .mention-fade-leave-to {
    transform: translateY(6px);
  }
}

.placement-bottom {
  .mention-fade-enter-from,
  .mention-fade-leave-to {
    transform: translateY(-6px);
  }
}

@keyframes mention-shimmer {
  0% {
    background-position: 200% 0;
  }

  100% {
    background-position: -200% 0;
  }
}
</style>
