<script setup lang="ts">
import { computed, ref, watch, type HTMLAttributes } from 'vue'
import { useFdsI18n } from '../../../plugin/useFdsI18n'
import FdsIcon from '../../FdsIcon/FdsIcon.vue'
import {
  countryCodeToFlag,
  filterCountryOptions,
  getCountryOption,
  isPrioritizedCountry,
  type CountryPhoneOption,
} from './countries'
import { useSharedClickOutside } from './useSharedClickOutside'

const { t, locale: fdsLocale } = useFdsI18n()

const country = defineModel<string>({ required: true })

const emit = defineEmits<{
  noCountryResults: [value: boolean]
}>()

const props = withDefaults(
  defineProps<{
    items: CountryPhoneOption[]
    valid?: boolean | null
    disabled?: boolean
    ariaLabel?: string
    dataTestid?: string
    class?: HTMLAttributes['class']
  }>(),
  {
    valid: undefined,
    disabled: false,
    ariaLabel: undefined,
    dataTestid: undefined,
    class: undefined,
  },
)

const rootRef = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLInputElement | null>(null)
const searchQuery = ref('')
const dropdownOpen = ref(false)
const isFocused = ref(false)
const hoveredCountry = ref<CountryPhoneOption | null>(null)
const activeIndex = ref(-1)

const listboxId = `fds-phone-country-listbox-${Math.random().toString(36).slice(2, 9)}`

const selectedCountry = computed(() => {
  const fromItems = props.items.find((item) => item.value === country.value)
  if (fromItems) {
    return fromItems
  }
  return getCountryOption(country.value, fdsLocale.value)
})

/** Options only while open — closed picker uses selectedCountry alone. */
const filteredCountries = computed(() => {
  if (!dropdownOpen.value) {
    return []
  }
  return filterCountryOptions(props.items, searchQuery.value)
})

const lastPrioritizedFilteredIndex = computed(() =>
  filteredCountries.value.reduce(
    (lastIndex, option, index) => (isPrioritizedCountry(option.value) ? index : lastIndex),
    -1,
  ),
)

const hasNoCountryResults = computed(
  () =>
    dropdownOpen.value &&
    !props.disabled &&
    searchQuery.value.trim().length > 0 &&
    filteredCountries.value.length === 0,
)

const isInvalid = computed(() => props.valid === false && !props.disabled)

const isSearching = computed(() => searchQuery.value.length > 0)

const isHoverPreview = computed(() => !!hoveredCountry.value && dropdownOpen.value)

const activeCountry = computed(() => {
  if (activeIndex.value < 0) {
    return null
  }
  return filteredCountries.value[activeIndex.value] ?? null
})

const showSelectedDialPreview = computed(() => !!selectedCountry.value && !isSearching.value && !isHoverPreview.value)

const showHoverDialPreview = computed(() => isHoverPreview.value && !!hoveredCountry.value)

const showActiveDialPreview = computed(
  () => !showHoverDialPreview.value && !!activeCountry.value && dropdownOpen.value && !isSearching.value,
)

const dialPreviewCountry = computed(() => {
  if (showHoverDialPreview.value && hoveredCountry.value) {
    return hoveredCountry.value
  }
  if (showActiveDialPreview.value && activeCountry.value) {
    return activeCountry.value
  }
  if (showSelectedDialPreview.value && selectedCountry.value) {
    return selectedCountry.value
  }
  return null
})

const activeDescendantId = computed(() =>
  activeIndex.value >= 0 ? `fds-phone-country-option-${activeIndex.value}` : undefined,
)

const inputDataAttrs = computed(() => (props.dataTestid ? { 'data-testid': props.dataTestid } : {}))

const listDataAttrs = computed(() => (props.dataTestid ? { 'data-testid': `${props.dataTestid}-list` } : {}))

const dialPreviewClasses = computed(() => [
  'pointer-events-none absolute inset-0 flex items-center pl-10 pr-10 text-base leading-6 tabular-nums',
  showHoverDialPreview.value ? 'text-gray-900/50' : 'text-gray-900',
])

const inputClasses = computed(() => [
  'block w-full h-12 rounded-md border border-gray-500 py-2 text-base leading-6 tabular-nums px-3 pr-10',
  'focus:outline-2 focus:outline-blue-500 -outline-offset-2 focus:border-transparent',
  props.disabled
    ? 'text-gray-800 outline-dashed outline-2 outline-gray-400 cursor-not-allowed border-transparent bg-gray-50'
    : 'cursor-text bg-white text-gray-900',
  isInvalid.value && 'outline-2 -outline-offset-2 outline-red-600',
  hasNoCountryResults.value && 'outline-red-600!',
])

const arrowButtonClasses = computed(() => [
  'absolute right-0 top-0 z-10 flex h-12 w-10 items-center justify-center border-0 bg-transparent p-0',
  props.disabled ? 'cursor-not-allowed' : 'cursor-pointer',
])

const arrowClasses = computed(() => [
  'pointer-events-none transition-transform duration-200 ease-in-out',
  {
    'fill-gray-500': props.disabled,
    'fill-blue-500': (!props.disabled && !isInvalid.value) || isInvalid.value,
    'rotate-180': dropdownOpen.value && !props.disabled,
  },
])

const openDropdown = () => {
  if (props.disabled) {
    return
  }
  dropdownOpen.value = true
  isFocused.value = true
  searchQuery.value = ''
  activeIndex.value = -1
}

const clearHoverPreview = () => {
  hoveredCountry.value = null
}

const clearActiveOption = () => {
  activeIndex.value = -1
}

const setActiveIndex = (index: number) => {
  if (!filteredCountries.value.length) {
    clearActiveOption()
    return
  }
  activeIndex.value = Math.max(0, Math.min(index, filteredCountries.value.length - 1))
}

const setActiveIndexToSelectedOrFirst = () => {
  const selectedIndex = filteredCountries.value.findIndex((option) => option.value === country.value)
  setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0)
}

const setActiveIndexToSelectedOrLast = () => {
  const selectedIndex = filteredCountries.value.findIndex((option) => option.value === country.value)
  setActiveIndex(selectedIndex >= 0 ? selectedIndex : filteredCountries.value.length - 1)
}

const onOptionMouseEnter = (option: CountryPhoneOption) => {
  hoveredCountry.value = option
}

const onOptionMouseLeave = () => {
  clearHoverPreview()
}

const closeDropdown = () => {
  dropdownOpen.value = false
  isFocused.value = false
  searchQuery.value = ''
  clearHoverPreview()
  clearActiveOption()
}

const onInputFocus = () => {
  openDropdown()
}

const onArrowPointerDown = () => {
  if (props.disabled) {
    return
  }
  if (dropdownOpen.value) {
    closeDropdown()
    inputRef.value?.blur()
    return
  }
  openDropdown()
  inputRef.value?.focus()
}

const onInputBlur = () => {
  isFocused.value = false
  window.setTimeout(() => {
    if (!dropdownOpen.value) {
      return
    }
    closeDropdown()
  }, 120)
}

const onSearchInput = (event: Event) => {
  const target = event.target as HTMLInputElement
  searchQuery.value = target.value
  clearActiveOption()
  if (!dropdownOpen.value) {
    dropdownOpen.value = true
  }
}

const onInputKeyDown = (event: KeyboardEvent) => {
  if (props.disabled) {
    return
  }

  const hasCountries = props.items.length > 0

  if (event.key === 'ArrowDown' && hasCountries) {
    event.preventDefault()
    clearHoverPreview()
    if (!dropdownOpen.value) {
      openDropdown()
      setActiveIndexToSelectedOrFirst()
      return
    }
    if (activeIndex.value < 0) {
      setActiveIndexToSelectedOrFirst()
      return
    }
    setActiveIndex(activeIndex.value + 1)
    return
  }

  if (event.key === 'ArrowUp' && hasCountries) {
    event.preventDefault()
    clearHoverPreview()
    if (!dropdownOpen.value) {
      openDropdown()
      setActiveIndexToSelectedOrLast()
      return
    }
    if (activeIndex.value < 0) {
      setActiveIndexToSelectedOrLast()
      return
    }
    setActiveIndex(activeIndex.value - 1)
    return
  }

  if (event.key === 'Enter') {
    if (!dropdownOpen.value && hasCountries) {
      event.preventDefault()
      openDropdown()
      setActiveIndexToSelectedOrFirst()
      return
    }

    if (dropdownOpen.value && activeCountry.value) {
      event.preventDefault()
      selectCountry(activeCountry.value)
    }
    return
  }

  if (event.key === 'Escape' && dropdownOpen.value) {
    event.preventDefault()
    closeDropdown()
    inputRef.value?.focus()
    return
  }

  if (event.key === 'Tab' && dropdownOpen.value) {
    closeDropdown()
  }
}

const selectCountry = (option: CountryPhoneOption) => {
  country.value = option.value
  closeDropdown()
  inputRef.value?.blur()
}

const onClickOutside = (event: MouseEvent) => {
  if (!dropdownOpen.value || !rootRef.value) {
    return
  }
  if (!rootRef.value.contains(event.target as Node)) {
    closeDropdown()
  }
}

useSharedClickOutside(onClickOutside, dropdownOpen)

watch(
  () => country.value,
  () => {
    if (!dropdownOpen.value) {
      searchQuery.value = ''
    }
  },
)

watch(filteredCountries, (options) => {
  if (!options.length) {
    clearActiveOption()
    return
  }
  if (activeIndex.value >= options.length) {
    setActiveIndex(options.length - 1)
  }
})

watch(
  hasNoCountryResults,
  (value) => {
    emit('noCountryResults', value)
  },
  { immediate: true },
)
</script>

<template>
  <div ref="rootRef" class="relative w-32" :class="props.class">
    <div class="relative w-full">
      <div v-if="!dropdownOpen" :class="dialPreviewClasses" aria-hidden="true">
        <span class="absolute left-3 top-1/2 -translate-y-1/2 text-xl leading-none">
          {{ countryCodeToFlag(dialPreviewCountry?.value ?? '') }}
        </span>
        {{ t('common.plus') }}{{ dialPreviewCountry?.countryCode ?? '' }}
      </div>
      <input
        ref="inputRef"
        type="search"
        autocomplete="off"
        role="combobox"
        :aria-expanded="dropdownOpen"
        aria-autocomplete="list"
        aria-haspopup="listbox"
        :aria-label="ariaLabel"
        :aria-controls="dropdownOpen ? listboxId : undefined"
        :aria-activedescendant="activeDescendantId"
        :value="searchQuery"
        :disabled="disabled"
        :class="inputClasses"
        v-bind="inputDataAttrs"
        @focus="onInputFocus"
        @blur="onInputBlur"
        @keydown="onInputKeyDown"
        @input="onSearchInput"
      />
      <button
        type="button"
        tabindex="-1"
        :disabled="disabled"
        :class="arrowButtonClasses"
        aria-hidden="true"
        @mousedown.prevent="onArrowPointerDown"
      >
        <FdsIcon name="arrowDown" :size="24" :class="arrowClasses" />
      </button>
    </div>
    <ul
      v-if="dropdownOpen && !disabled && filteredCountries.length > 0"
      role="listbox"
      :id="listboxId"
      class="absolute left-0 z-20 mt-2 min-w-[20rem] w-max max-w-[calc(100vw-2rem)] max-h-[280px] overflow-y-auto rounded-md border border-gray-300 bg-white"
      v-bind="listDataAttrs"
      @mouseleave="clearHoverPreview"
    >
      <li
        v-for="(option, index) in filteredCountries"
        :key="option.value"
        :id="`fds-phone-country-option-${index}`"
        role="option"
        :aria-selected="option.value === country"
        class="mb-0 border-b border-gray-100 hover:bg-blue_t-100 active:bg-blue_t-200 last:border-b-0!"
        :class="{
          'border-gray-300': index === lastPrioritizedFilteredIndex,
          'bg-blue_t-100': activeIndex === index,
        }"
      >
        <button
          type="button"
          tabindex="-1"
          class="flex w-full items-center justify-between gap-2 py-4 px-3 text-left cursor-pointer"
          @mouseenter="onOptionMouseEnter(option)"
          @mouseleave="onOptionMouseLeave"
          @mousedown.prevent
          @click="selectCountry(option)"
        >
          <span class="flex items-center gap-2">
            <span class="flex items-center gap-3">
              <span class="shrink-0 text-xl leading-none" aria-hidden="true">
                {{ countryCodeToFlag(option.value) }}
              </span>
              {{ option.countryName }}
            </span>
            <span class="text-gray-600">({{ t('common.plus') }}{{ option.countryCode }})</span>
          </span>
          <span aria-hidden="true">
            <FdsIcon v-if="option.value === country" name="check" :size="24" class="fill-green-600" />
          </span>
        </button>
      </li>
    </ul>
  </div>
</template>
