<template>
  <div class="select-field">
    <label v-if="label" :for="inputId" class="select-field__label">{{ label }}</label>

    <div
      :id="inputId"
      ref="referenceEl"
      tabindex="0"
      class="select-field__trigger"
      :class="{
        'select-field__trigger--error': error,
        'select-field__trigger--disabled': disabled,
        'select-field__trigger--open': isOpen,
      }"
      @click="toggle"
    >
      <span class="select-field__value">{{ selectedLabel }}</span>
      <SvgIcon
        name="caret-down"
        :size="20"
        class="select-field__caret"
        :class="{ 'select-field__caret--open': isOpen }"
      />
    </div>

    <div v-if="error" class="select-field__error" :class="{ 'animate-shake': vibration }">
      {{ error }}
    </div>

    <Teleport to="body">
      <Transition name="dropdown">
        <div
          v-if="isOpen"
          ref="floatingEl"
          class="select-field__dropdown scrollbar-thin"
          :style="{ width: dropdownWidth }"
        >
          <div v-if="searchable" class="select-field__search">
            <input
              v-model="searchQuery"
              type="text"
              placeholder="جستجو..."
              class="select-field__search-input"
              @input="handleSearch"
            />
          </div>

          <div v-if="options.length === 0" class="select-field__empty">موردی یافت نشد</div>

          <ul v-else class="select-field__list">
            <li v-for="(opt, i) in options" :key="i">
              <button
                type="button"
                class="select-field__option"
                :class="{ 'select-field__option--active': isSelected(opt) }"
                @click="select(opt)"
              >
                <template v-if="multiple">
                  <SvgIcon
                    :name="isSelected(opt) ? 'check-square' : 'square'"
                    :size="16"
                    class="select-field__check"
                  />
                </template>
                <span class="select-field__option-text">{{ opt[optionLabel] }}</span>
              </button>
            </li>
          </ul>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<script setup>
import { computed, onBeforeUnmount, ref } from 'vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'

let uid = 0
const nextUid = () => `select-field-${++uid}`

const props = defineProps({
  modelValue: { type: [String, Number, Array, Boolean, Object], default: undefined },
  name: { type: String, default: '' },
  label: { type: String, default: '' },
  placeholder: { type: String, default: 'انتخاب کنید' },
  options: { type: Array, required: true },
  optionLabel: { type: String, default: 'name' },
  optionValue: { type: String, default: 'id' },
  multiple: { type: Boolean, default: false },
  disabled: { type: Boolean, default: false },
  error: { type: String, default: '' },
  vibration: { type: Boolean, default: false },
  searchable: { type: Boolean, default: false },
  onSearch: { type: Function, default: null },
  searchDebounce: { type: Number, default: 400 },
})

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

const inputId = computed(() => props.name || nextUid())
const referenceEl = ref(null)
const floatingEl = ref(null)
const isOpen = ref(false)
const dropdownWidth = ref('auto')
const searchQuery = ref('')
let cleanup = null
let debounceTimer = null

const isSelected = (opt) => {
  const value = opt[props.optionValue]
  return props.multiple
    ? Array.isArray(props.modelValue) && props.modelValue.includes(value)
    : props.modelValue === value
}

const selectedLabel = computed(() => {
  const value = props.modelValue
  if (value === undefined || value === null || value === '') return props.placeholder
  if (props.multiple && Array.isArray(value)) {
    const selected = props.options.filter((o) => value.includes(o[props.optionValue]))
    if (selected.length === 0) return props.placeholder
    if (selected.length <= 2) return selected.map((s) => s[props.optionLabel]).join('، ')
    return `${selected.length} مورد انتخاب شده`
  }
  const found = props.options.find((o) => o[props.optionValue] === value)
  return found ? found[props.optionLabel] : props.placeholder
})

const select = (opt) => {
  if (props.disabled) return
  const value = opt[props.optionValue]
  if (props.multiple) {
    let current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
    if (current.includes(value)) {
      current = current.filter((v) => v !== value)
    } else {
      current.push(value)
    }
    emit('update:modelValue', current)
    emit('change', current)
  } else {
    emit('update:modelValue', value)
    emit('change', value)
    close()
  }
}

const mountFloating = async () => {
  await Promise.resolve()
  const reference = referenceEl.value
  const floating = floatingEl.value
  if (!reference || !floating) return
  dropdownWidth.value = `${reference.offsetWidth}px`
  cleanup = autoUpdate(reference, floating, () => {
    computePosition(reference, floating, {
      placement: 'bottom-start',
      middleware: [offset(4), flip(), shift({ padding: 8 })],
    }).then(({ x, y }) => {
      Object.assign(floating.style, { left: `${x}px`, top: `${y}px`, position: 'absolute' })
    })
  })
  document.addEventListener('mousedown', handleOutsideClick)
}

const unmountFloating = () => {
  cleanup?.()
  cleanup = null
  document.removeEventListener('mousedown', handleOutsideClick)
}

const handleOutsideClick = (event) => {
  const reference = referenceEl.value
  const floating = floatingEl.value
  if (
    reference &&
    !reference.contains(event.target) &&
    floating &&
    !floating.contains(event.target)
  ) {
    close()
  }
}

const open = () => {
  isOpen.value = true
  setTimeout(mountFloating, 0)
}

const close = () => {
  isOpen.value = false
  searchQuery.value = ''
  unmountFloating()
}

const toggle = () => {
  if (props.disabled) return
  isOpen.value ? close() : open()
}

const handleSearch = () => {
  if (!props.onSearch) return
  clearTimeout(debounceTimer)
  debounceTimer = setTimeout(() => props.onSearch(searchQuery.value), props.searchDebounce)
}

onBeforeUnmount(() => unmountFloating())
</script>

<style lang="scss" scoped>
.select-field {
  position: relative;
  width: 100%;
  margin-bottom: 0.75rem;

  &__label {
    display: flex;
    margin-bottom: 0.25rem;
    font-family: var(--font-family-fa);
    font-weight: 300;
    line-height: 1.5rem;
    font-size: 0.875rem;
    color: var(--color-prim-gray);
    pointer-events: none;
  }

  &__trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    height: 2.35rem;
    padding: 0 1.25rem;
    border: 1px solid var(--color-thd-gray);
    border-radius: 1.5rem;
    background: transparent;
    cursor: pointer;
    font-family: var(--font-family-number-fa);
    font-size: 0.9rem;
    transition: all 0.2s;
    outline: none;

    &:focus {
      box-shadow: 0 0 0 1px var(--color-validate);
    }

    &--error {
      border-color: var(--color-error);

      &:focus {
        box-shadow: 0 0 0 1px var(--color-error);
      }
    }

    &--disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
  }

  &__value {
    flex: 1;
    text-align: start;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  &__caret {
    color: var(--color-thd-gray);
    transition: transform 0.2s;
    flex-shrink: 0;

    &--open {
      transform: rotate(180deg);
    }
  }

  &__error {
    margin-top: 0.25rem;
    font-family: var(--font-family-fa);
    font-size: 0.75rem;
    color: var(--color-error);
  }

  &__dropdown {
    position: absolute;
    z-index: 9999;
    max-height: 15rem;
    overflow-y: auto;
    border: 1px solid var(--color-thd-gray);
    border-radius: 1rem;
    background: #fff;
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 10%), 0 4px 6px -4px rgba(0, 0, 0, 10%);
  }

  &__search {
    padding: 0.75rem;
  }

  &__search-input {
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-family: var(--font-family-fa);
    font-size: 0.875rem;
    border: 1px solid var(--color-thd-gray);
    border-radius: 0.75rem;
    outline: none;

    &:focus {
      box-shadow: 0 0 0 1px var(--color-validate);
    }
  }

  &__empty {
    padding: 0.75rem;
    font-family: var(--font-family-fa);
    font-size: 0.875rem;
    color: var(--color-prim-gray);
  }

  &__list {
    list-style: none;
    margin: 0;
    padding: 0.25rem;

    > li + li {
      margin-top: 0.25rem;
    }
  }

  &__option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.25rem 0.75rem;
    border: none;
    border-radius: 1.5rem;
    background: transparent;
    text-align: right;
    font-family: var(--font-family-fa);
    cursor: pointer;
    transition: background-color 0.15s;

    &:hover {
      background-color: #f3f4f6;
    }

    &--active {
      background-color: #f3f4f6;
    }
  }

  &__check {
    color: var(--color-thd-gray);
    flex-shrink: 0;
  }

  &__option-text {
    font-size: 0.9rem;
  }
}

.dropdown-enter-active,
.dropdown-leave-active {
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.dropdown-enter-from,
.dropdown-leave-to {
  opacity: 0;
  transform: translateY(-6px);
}
</style>
