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

const props = defineProps<{
  options: { value: string; label: string }[];
  modelValue: string;
  placeholder: string;
  width: string;
}>();

const emit = defineEmits(['update:modelValue']);

const showOptions = ref(false);
const selectedValueRef = elementRef<HTMLElement | null>(null);
const optionsListRef = elementRef<HTMLElement | null>(null);

const handleClickOutside = (event: MouseEvent) => {
  if (selectedValueRef.value && optionsListRef.value && showOptions.value) {
    const isClickInside = selectedValueRef.value.contains(event.target as Node) || optionsListRef.value.contains(event.target as Node);
    if (!isClickInside) {
      showOptions.value = false;
    }
  }
};

const handleSelect = (option: { value: string; label: string }) => {
  emit('update:modelValue', option.value);
  showOptions.value = false;
};

const selectedOption = computed(() => {
  return props.options.find(option => option.value === props.modelValue);
});

let updateTimer: number | null = null;

const updateOptionsListPosition = () => {
  if (updateTimer) {
    clearTimeout(updateTimer);
  }

  updateTimer = setTimeout(() => {
    if (selectedValueRef.value && optionsListRef.value && showOptions.value) {
      optionsListRef.value.style.top = selectedValueRef.value.getBoundingClientRect().bottom + 'px';
      optionsListRef.value.style.left = selectedValueRef.value.getBoundingClientRect().left + 'px';
      optionsListRef.value.style.width = selectedValueRef.value.offsetWidth + 'px';
    }
    updateTimer = null;
  }, 300);
};

onMounted(() => {
  document.addEventListener('click', handleClickOutside);
  window.addEventListener('resize', updateOptionsListPosition);
});

onUnmounted(() => {
  document.removeEventListener('click', handleClickOutside);
  window.removeEventListener('resize', updateOptionsListPosition);
});

watch(showOptions, (newValue) => {
  if (newValue) {
    updateOptionsListPosition();
  }
});

</script>

<template>
  <div class="better-select" :class="showOptions ? ['showing-select'] : []">
    <div ref="selectedValueRef" :class="selectedOption ? [] : ['placeholder-showing']" class="selected-value" @click="showOptions = !showOptions">
      {{ selectedOption?.label || props.placeholder }}
    </div>
    <ul
      ref="optionsListRef"
      class="options-list"
      :class="showOptions ? ['showing-options-list'] : []"
      :style="{
        position: 'fixed',
        top: selectedValueRef?.getBoundingClientRect().bottom + 'px',
        left: selectedValueRef?.getBoundingClientRect().left + 'px',
        width: selectedValueRef?.offsetWidth + 'px'
      }"
    >
      <li
        v-for="option in props.options"
        :key="option.value"
        :class="option.value == selectedOption?.value ? ['active'] : []"
        @click="handleSelect(option)"
      >
        {{ option.label }}
      </li>
    </ul>
  </div>
</template>

<style scoped>
.better-select {
  position: relative;
  text-align: center;
  width: v-bind("props.width");
}

.selected-value:hover {
  background-size: 100% 2px;
}

.showing-select .selected-value {
  background-size: 100% 2px;
}

.selected-value {
  padding: 2px;
  cursor: pointer;
  background: linear-gradient(to right, var(--text-color), var(--text-color)) no-repeat left bottom;
  color: var(--text-color);
  background-size: 0 2px;
  transition: all .3s;
}

.placeholder-showing {
  color: var(--secondary-text-color);
}

.options-list {
  margin: 0;
  padding: 0;
  opacity: 0;
  list-style-type: none;
  border-top: none;
  border-radius: 0 0 4px 4px;
  max-height: 200px;
  overflow-y: hidden;
  background-color: var(--background-color);
  z-index: 1000;
  transform-origin: top;
  transform: scaleY(0%);
  transition: all .3s;
}

.showing-options-list {
  opacity: 100%;
  transform: scaleY(100%);
  box-shadow: var(--muted-text-color) 0 0 4px;
}

.options-list li {
  padding: 4px 0;
  cursor: pointer;
  text-align: center;
  transition: all .3s;
}

.options-list .active {
  background-color: var(--secondary-background-color);
}

.options-list li:hover {
  background-color: var(--secondary-background-color);
}
</style>
