<template>
  <Teleport to="body">
    <div
      v-if="open"
      ref="panelRef"
      class="global-select-layer"
      @pointerdown.stop
      @mousedown.stop
      @click.stop
    >
      <div class="global-select-input-box" :style="inputStyle">
        <input
          ref="searchInputRef"
          v-model.trim="searchText"
          class="global-select-search-input"
          type="text"
          :placeholder="selectedLabel || '请输入关键字搜索'"
          @keydown.down.prevent="moveActive(1)"
          @keydown.up.prevent="moveActive(-1)"
          @keydown.enter.prevent="chooseActive"
          @keydown.esc.prevent="closePanel"
        />
        <span class="global-select-arrow" aria-hidden="true"></span>
      </div>

      <div class="global-select-panel" :style="panelStyle">
        <div class="global-select-option-list">
          <button
            v-for="(option, index) in filteredOptions"
            :key="`${option.index}-${option.value}`"
            class="global-select-option"
            :class="{
              'global-select-option-active': index === activeIndex,
              'global-select-option-selected': option.selected,
              'global-select-option-disabled': option.disabled
            }"
            type="button"
            :disabled="option.disabled"
            @mouseenter="activeIndex = index"
            @click="selectOption(option)"
          >
            <span class="global-select-option-main">{{ option.display.title }}</span>
            <span v-if="option.display.subtitle" class="global-select-option-sub">{{ option.display.subtitle }}</span>
            <span v-if="option.display.meta" class="global-select-option-meta">{{ option.display.meta }}</span>
          </button>
          <div v-if="!filteredOptions.length" class="global-select-empty">没有匹配选项</div>
        </div>
      </div>
    </div>
  </Teleport>
</template>

<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";

const open = ref(false);
const activeSelect = ref(null);
const options = ref([]);
const searchText = ref("");
const activeIndex = ref(0);
const panelRef = ref(null);
const searchInputRef = ref(null);
const panelPosition = ref({
  left: 0,
  top: 0,
  width: 280,
  height: 48,
  maxHeight: 360
});

const selectedLabel = computed(() => {
  const selectedOptionLabel = cleanOptionLabel(activeSelect.value?.selectedOptions?.[0]?.textContent);
  if (selectedOptionLabel) {
    return selectedOptionLabel;
  }
  const selected = options.value.find((option) => option.selected);
  return selected?.label || selected?.value || "";
});

const inputStyle = computed(() => ({
  left: `${panelPosition.value.left}px`,
  top: `${panelPosition.value.top}px`,
  width: `${panelPosition.value.width}px`,
  height: `${panelPosition.value.height}px`
}));

const panelStyle = computed(() => ({
  left: `${panelPosition.value.left}px`,
  top: `${panelPosition.value.top + panelPosition.value.height + 6}px`,
  width: `${panelPosition.value.width}px`,
  maxHeight: `${panelPosition.value.maxHeight}px`
}));

const filteredOptions = computed(() => {
  const keywords = normalizeText(searchText.value).split(/\s+/).filter(Boolean);
  if (!keywords.length) {
    return options.value;
  }
  return options.value.filter((option) => {
    const searchBody = normalizeText(`${option.label} ${option.value}`);
    return keywords.every((keyword) => searchBody.includes(keyword));
  });
});

watch(filteredOptions, (rows) => {
  if (!rows.length) {
    activeIndex.value = 0;
    return;
  }
  if (activeIndex.value >= rows.length) {
    activeIndex.value = rows.length - 1;
  }
  if (activeIndex.value < 0) {
    activeIndex.value = 0;
  }
});

function normalizeText(value) {
  return String(value || "").trim().toLowerCase();
}

function cleanOptionLabel(value) {
  return String(value || "").replace(/\s+/g, " ").trim();
}

function splitOptionLabel(label) {
  const parts = cleanOptionLabel(label).split(" · ").map((part) => part.trim()).filter(Boolean);
  return {
    title: parts[0] || cleanOptionLabel(label) || "空选项",
    subtitle: parts[1] || "",
    meta: parts.slice(2).join(" · ")
  };
}

function isSearchableSelect(target) {
  return target instanceof HTMLSelectElement && !target.disabled && target.size <= 1 && !target.multiple;
}

function isPanelPlaceholderOption(option) {
  return Boolean(option?.disabled && option?.value === "");
}

function collectOptions(selectEl) {
  return Array.from(selectEl.options || [])
    .map((option, index) => ({ option, index }))
    .filter(({ option }) => !isPanelPlaceholderOption(option))
    .map(({ option, index }) => {
      const label = cleanOptionLabel(option.textContent);
      return {
        index,
        value: option.value,
        label,
        display: splitOptionLabel(label),
        disabled: Boolean(option.disabled || option.parentElement?.disabled),
        selected: index === selectEl.selectedIndex
      };
    });
}

function calculatePanelPosition(selectEl) {
  const rect = selectEl.getBoundingClientRect();
  const margin = 8;
  const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  const width = Math.max(rect.width, 180);
  const left = Math.min(Math.max(rect.left, margin), Math.max(viewportWidth - width - margin, margin));
  const top = Math.min(Math.max(rect.top, margin), Math.max(viewportHeight - rect.height - margin, margin));
  const belowSpace = viewportHeight - top - rect.height - margin - 6;
  const maxHeight = Math.max(Math.min(belowSpace, 320), 160);

  panelPosition.value = {
    left,
    top,
    width,
    height: Math.max(rect.height, 40),
    maxHeight
  };
}

function openForSelect(selectEl) {
  activeSelect.value = selectEl;
  options.value = collectOptions(selectEl);
  searchText.value = "";
  const selectedIndex = options.value.findIndex((option) => option.selected && !option.disabled);
  activeIndex.value = selectedIndex >= 0 ? selectedIndex : Math.max(options.value.findIndex((option) => !option.disabled), 0);
  calculatePanelPosition(selectEl);
  open.value = true;
  nextTick(() => {
    searchInputRef.value?.focus();
    searchInputRef.value?.select();
  });
}

function closePanel() {
  open.value = false;
  activeSelect.value = null;
  options.value = [];
  searchText.value = "";
  activeIndex.value = 0;
}

function moveActive(delta) {
  const rows = filteredOptions.value;
  if (!rows.length) {
    return;
  }
  let nextIndex = activeIndex.value;
  for (let step = 0; step < rows.length; step += 1) {
    nextIndex = (nextIndex + delta + rows.length) % rows.length;
    if (!rows[nextIndex]?.disabled) {
      activeIndex.value = nextIndex;
      return;
    }
  }
}

function chooseActive() {
  const option = filteredOptions.value[activeIndex.value];
  if (option) {
    selectOption(option);
  }
}

function selectOption(option) {
  const selectEl = activeSelect.value;
  if (!selectEl || option.disabled) {
    return;
  }

  selectEl.selectedIndex = option.index;
  selectEl.dispatchEvent(new Event("input", { bubbles: true }));
  selectEl.dispatchEvent(new Event("change", { bubbles: true }));
  selectEl.focus({ preventScroll: true });
  closePanel();
}

function handlePointerDown(event) {
  const target = event.target;
  if (panelRef.value?.contains(target)) {
    return;
  }
  if (!isSearchableSelect(target)) {
    if (open.value) {
      closePanel();
    }
    return;
  }
  event.preventDefault();
  event.stopPropagation();
  openForSelect(target);
}

function handleMouseDown(event) {
  if ("PointerEvent" in window) {
    return;
  }
  handlePointerDown(event);
}

function handleKeyDown(event) {
  if (open.value) {
    if (event.key === "Escape") {
      event.preventDefault();
      closePanel();
    }
    return;
  }

  if (
    isSearchableSelect(event.target) &&
    ["Enter", " ", "ArrowDown", "ArrowUp"].includes(event.key)
  ) {
    event.preventDefault();
    openForSelect(event.target);
  }
}

function handleViewportChange() {
  if (open.value && activeSelect.value) {
    calculatePanelPosition(activeSelect.value);
  }
}

onMounted(() => {
  document.addEventListener("pointerdown", handlePointerDown, true);
  document.addEventListener("mousedown", handleMouseDown, true);
  document.addEventListener("keydown", handleKeyDown, true);
  window.addEventListener("resize", handleViewportChange);
  window.addEventListener("scroll", handleViewportChange, true);
});

onUnmounted(() => {
  document.removeEventListener("pointerdown", handlePointerDown, true);
  document.removeEventListener("mousedown", handleMouseDown, true);
  document.removeEventListener("keydown", handleKeyDown, true);
  window.removeEventListener("resize", handleViewportChange);
  window.removeEventListener("scroll", handleViewportChange, true);
});
</script>

<style scoped>
.global-select-layer {
  position: fixed;
  inset: 0;
  z-index: var(--ui-z-global-select, 3100);
  pointer-events: none;
}

.global-select-input-box {
  position: fixed;
  pointer-events: auto;
}

.global-select-search-input {
  width: 100%;
  height: 100%;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  background: #ffffff;
  color: #606266;
  font: inherit;
  font-weight: 400;
  outline: none;
  padding: 0 34px 0 12px;
  box-shadow:
    0 0 0 2px rgba(64, 158, 255, 0.1),
    0 6px 16px rgba(31, 45, 61, 0.12);
}

.global-select-search-input:focus {
  border-color: #409eff;
  box-shadow:
    0 0 0 2px rgba(64, 158, 255, 0.12),
    0 6px 16px rgba(31, 45, 61, 0.12);
}

.global-select-search-input::placeholder {
  color: #c0c4cc;
}

.global-select-arrow {
  position: absolute;
  right: 13px;
  top: 50%;
  width: 8px;
  height: 8px;
  border-bottom: 1.5px solid #c0c4cc;
  border-right: 1.5px solid #c0c4cc;
  pointer-events: none;
  transform: translateY(-65%) rotate(45deg);
}

.global-select-panel {
  position: fixed;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  background: #ffffff;
  box-shadow: 0 8px 24px rgba(31, 45, 61, 0.14);
  backdrop-filter: none;
  pointer-events: auto;
}

.global-select-option-list {
  overflow: auto;
  padding: 6px 0;
}

.global-select-option {
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 3px 10px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: #606266;
  cursor: pointer;
  font: inherit;
  min-height: 34px;
  padding: 8px 14px;
  text-align: left;
}

.global-select-option-main {
  grid-column: 1;
  grid-row: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.global-select-option-sub {
  grid-column: 1 / -1;
  grid-row: 2;
  min-width: 0;
}

.global-select-option-meta {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.global-select-option small {
  color: #909399;
  font-size: 12px;
}

.global-select-option:hover,
.global-select-option-active {
  color: #409eff;
  background: #f5f7fa;
}

.global-select-option-selected {
  color: #409eff;
  font-weight: 600;
  background: #ecf5ff;
}

.global-select-option-disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.global-select-empty {
  color: #909399;
  padding: 18px 12px;
  text-align: center;
}
</style>
