<template>
  <button class="search-trigger" :class="{ 'search-trigger--light': light }" @click="openDialog">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="11" cy="11" r="7" />
      <path d="m21 21-4.35-4.35" />
    </svg>
    <span class="search-trigger__label">全局搜索</span>
    <span class="search-trigger__shortcut">Ctrl/Cmd + K</span>
  </button>

  <el-dialog
    v-model="visible"
    title="全局快速搜索"
    width="720px"
    append-to-body
    destroy-on-close
    :close-on-click-modal="true"
    @closed="handleClosed"
  >
    <div class="global-search">
      <el-input
        ref="searchInputRef"
        v-model="keyword"
        clearable
        size="large"
        placeholder="搜索菜单、会议、用户"
        @keydown.down.prevent="moveActive(1)"
        @keydown.up.prevent="moveActive(-1)"
        @keydown.enter.prevent="handleEnter"
      />

      <div class="global-search__hint">回车跳转，方向键切换结果</div>

      <div v-if="loading" class="global-search__state">搜索中...</div>
      <div v-else-if="!trimmedKeyword" class="global-search__state">输入关键词开始搜索</div>
      <div v-else-if="indexedGroups.length === 0" class="global-search__state">未找到匹配结果</div>

      <div v-else class="global-search__results">
        <section v-for="group in indexedGroups" :key="group.type" class="global-search__group">
          <div class="global-search__group-title">{{ group.label }}</div>
          <button
            v-for="item in group.items"
            :key="`${item.type}-${item.routePath}-${item.globalIndex}`"
            class="global-search__item"
            :class="{ 'global-search__item--active': item.globalIndex === activeIndex }"
            @mouseenter="activeIndex = item.globalIndex"
            @click="handleSelect(item.routePath)"
          >
            <div class="global-search__item-main">
              <span class="global-search__item-title">{{ item.title }}</span>
              <el-tag v-if="item.badge" size="small" effect="plain">{{ item.badge }}</el-tag>
            </div>
            <div class="global-search__item-subtitle">{{ item.subtitle || item.routePath }}</div>
          </button>
        </section>
      </div>
    </div>
  </el-dialog>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { fetchGlobalSearch } from "../api/modules";

type SearchItem = {
  type: string;
  title: string;
  subtitle?: string;
  routePath: string;
  badge?: string;
};

type SearchGroup = {
  type: string;
  label: string;
  items: SearchItem[];
};

defineProps<{
  light?: boolean;
}>();

const router = useRouter();
const visible = ref(false);
const keyword = ref("");
const loading = ref(false);
const groups = ref<SearchGroup[]>([]);
const activeIndex = ref(-1);
const searchInputRef = ref<any>(null);
let searchTimer: number | null = null;

const trimmedKeyword = computed(() => keyword.value.trim());
const indexedGroups = computed(() => {
  let cursor = 0;
  return groups.value.map((group) => ({
    ...group,
    items: (group.items || []).map((item) => ({
      ...item,
      globalIndex: cursor++,
    })),
  }));
});
const flatItems = computed(() => indexedGroups.value.flatMap((group) => group.items));

const focusSearchInput = async () => {
  await nextTick();
  searchInputRef.value?.focus?.();
};

const openDialog = async () => {
  visible.value = true;
  await focusSearchInput();
};

const closeDialog = () => {
  visible.value = false;
};

const runSearch = async () => {
  const currentKeyword = trimmedKeyword.value;
  if (!currentKeyword) {
    groups.value = [];
    activeIndex.value = -1;
    return;
  }
  loading.value = true;
  try {
    const resp = await fetchGlobalSearch({ q: currentKeyword, limitPerType: 5 });
    groups.value = Array.isArray(resp?.data?.groups) ? resp.data.groups : [];
    activeIndex.value = flatItems.value.length > 0 ? 0 : -1;
  } finally {
    loading.value = false;
  }
};

const scheduleSearch = () => {
  if (searchTimer !== null) {
    window.clearTimeout(searchTimer);
  }
  searchTimer = window.setTimeout(() => {
    searchTimer = null;
    runSearch();
  }, 180);
};

const moveActive = (step: number) => {
  const items = flatItems.value;
  if (!items.length) {
    activeIndex.value = -1;
    return;
  }
  const currentIndex = activeIndex.value < 0 ? 0 : activeIndex.value;
  activeIndex.value = (currentIndex + step + items.length) % items.length;
};

const handleEnter = async () => {
  const current = flatItems.value[activeIndex.value];
  if (!current?.routePath) {
    return;
  }
  await handleSelect(current.routePath);
};

const handleSelect = async (routePath: string) => {
  if (!routePath) {
    return;
  }
  closeDialog();
  await router.push(routePath);
};

const handleClosed = () => {
  keyword.value = "";
  groups.value = [];
  activeIndex.value = -1;
  loading.value = false;
};

const handleGlobalShortcut = (event: KeyboardEvent) => {
  if (!(event.ctrlKey || event.metaKey) || String(event.key || "").toLowerCase() !== "k") {
    return;
  }
  event.preventDefault();
  if (!visible.value) {
    openDialog();
    return;
  }
  focusSearchInput();
};

watch(
  () => trimmedKeyword.value,
  () => {
    if (!visible.value) {
      return;
    }
    scheduleSearch();
  },
);

watch(
  () => visible.value,
  (value) => {
    if (value) {
      focusSearchInput();
      return;
    }
    if (searchTimer !== null) {
      window.clearTimeout(searchTimer);
      searchTimer = null;
    }
  },
);

onMounted(() => {
  window.addEventListener("keydown", handleGlobalShortcut);
});

onUnmounted(() => {
  window.removeEventListener("keydown", handleGlobalShortcut);
  if (searchTimer !== null) {
    window.clearTimeout(searchTimer);
    searchTimer = null;
  }
});
</script>

<style scoped>
.search-trigger {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 36px;
  padding: 0 12px;
  border: none;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.1);
  color: #ffffff;
  cursor: pointer;
}

.search-trigger--light {
  background: var(--wo-light-action-bg);
  color: var(--wo-light-action-text);
}

.search-trigger svg {
  width: 16px;
  height: 16px;
}

.search-trigger__label {
  font-size: 13px;
  font-weight: 600;
}

.search-trigger__shortcut {
  font-size: 11px;
  opacity: 0.72;
  white-space: nowrap;
}

.global-search {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.global-search__hint,
.global-search__state {
  font-size: 13px;
  color: var(--el-text-color-secondary);
}

.global-search__results {
  display: flex;
  flex-direction: column;
  gap: 18px;
  max-height: 480px;
  overflow: auto;
  padding-right: 4px;
}

.global-search__group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.global-search__group-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--el-color-primary);
  letter-spacing: 0.08em;
}

.global-search__item {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--wo-card-border);
  border-radius: 12px;
  background: var(--el-bg-color-overlay);
  text-align: left;
  cursor: pointer;
  transition: all 0.2s ease;
}

.global-search__item:hover,
.global-search__item--active {
  border-color: var(--el-color-primary-light-3);
  background: color-mix(in srgb, var(--el-color-primary-light-9) 72%, transparent);
}

.global-search__item-main {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.global-search__item-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--el-text-color-primary);
}

.global-search__item-subtitle {
  font-size: 12px;
  color: var(--el-text-color-secondary);
  line-height: 1.5;
  word-break: break-all;
}

html.dark .global-search__item {
  border-color: var(--wo-search-border);
  background: var(--wo-search-surface);
}

html.dark .global-search__item:hover,
html.dark .global-search__item--active {
  background: var(--wo-search-surface-hover);
  border-color: var(--el-color-primary-light-3);
}

html.dark .global-search__hint,
html.dark .global-search__state,
html.dark .global-search__item-subtitle {
  color: var(--wo-search-muted);
}

html.dark .global-search__item-title {
  color: var(--wo-search-title);
}

@media (max-width: 768px) {
  .search-trigger__shortcut {
    display: none;
  }
}
</style>
