<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useBoldQuery } from '../../composables/useBoldQuery'
import {
  formatPidWithDash,
  isPidString,
  normalizePidSearchValue,
  PID_MASK,
  PID_MASK_OPTIONS,
  useIsPid,
} from '../../composables/useIsPid'
import { debounce } from '../../helpers/debounce'
import { useFdsI18n } from '../../plugin/useFdsI18n'
import FdsButtonIcon from '../Buttons/FdsButtonIcon/FdsButtonIcon.vue'
import FdsSpinner from '../FdsSpinner/FdsSpinner.vue'
import FdsCheckbox from '../Form/FdsCheckbox/FdsCheckbox.vue'
import FdsInput from '../Form/FdsInput/FdsInput.vue'
import FdsRadio from '../Form/FdsRadio/FdsRadio.vue'
import FdsMeta from '../Typography/FdsMeta/FdsMeta.vue'
import type { FdsSearchSelectProProps } from './types'
import { useSearchSelectProItems } from './useSearchSelectProItems'

const props = withDefaults(defineProps<FdsSearchSelectProProps>(), {
  items: () => [],
  multiple: false,
  hierarchical: false,
  selectChildrenRecursively: false,
  initialSelectedItems: undefined,
  itemKey: undefined,
  dividerField: 'isDivider',
  dividerSelectable: false,
  autoDividerBy: undefined,
  levelField: 'level',
  showSelectedToggle: false,
  selectedToggleLabel: 'Dina val',
  showLoadMore: false,
  loadingMore: false,
  autoLoadOnScroll: false,
  autoLoadThreshold: 24,
  highlightDividerMatch: true,
  showOutOfBoundsDivider: true,
  maxItems: undefined,
  page: undefined,
  totalPages: undefined,
  totalCount: undefined,
  loading: false,
  searchFields: () => [],
  preserveOrder: false,
  debounceMs: 500,
  initialValue: '',
  displayValue: '',
  disabled: false,
  dropdownAbsolute: false,
  labelLeft: false,
  label: '',
  meta: undefined,
  searchContext: () => ({ context: '', linkWord: '' }),
  valid: undefined,
  validIconOnSelected: false,
  invalidOnEmptySelection: true,
  invalidMessage: '',
  noResultPrompt: '',
  maxListHeight: undefined,
  unspecifiedLabel: undefined,
  dataTestid: undefined,
})

const searchFields = computed(() => props.searchFields ?? [])
const { t } = useFdsI18n()

const emit = defineEmits<{
  (e: 'searchSelected', value: Record<string, unknown> | Array<Record<string, unknown>> | null): void
  (e: 'paginate', value: number): void
  (e: 'total', value: number): void
  (e: 'change', value: string): void
  (e: 'input', ev: Event): void
  (e: 'clearInput', ev: Event): void
  (e: 'loadMore'): void
}>()

const dropdownVisible = ref(false)
const searchTerm = ref('')
const bypassSearchFilter = ref(false)
const selectedItem = ref<Record<string, unknown> | null>(null)
const selectedItems = ref<Array<Record<string, unknown>>>([...(props.initialSelectedItems ?? [])])
const showSelectedOnly = ref(false)
const inputHasFocus = ref(false)
const valid = ref<boolean | null>(props.valid ?? null)
const isMultiple = computed(() => props.multiple)
const totalMatchingItems = ref(0)

// Use prop.totalCount if provided, otherwise calculate from items.length
const totalCount = computed(() => {
  if (props.totalCount !== undefined) {
    return props.totalCount
  }
  return props.items?.length || 0
})
// Personnummer detection using composable
const { isPid } = useIsPid(searchTerm)

const {
  displayedItems,
  filterAndPaginate: runFilterAndPaginate,
  getItemIdentifier,
  isDividerItem,
  isItemSelected,
  optionPaddingStyle,
  sourceItems,
  resetVisibleItemsLimit,
  shouldShowLoadMore,
  handleInternalLoadMore,
  syncSelectedItemsWithItems,
} = useSearchSelectProItems({
  props,
  searchFields,
  searchTerm,
  bypassSearchFilter,
  showSelectedOnly,
  selectedItems,
  isMultiple,
  isPid,
})

const filterAndPaginate = () =>
  runFilterAndPaginate((count) => {
    totalMatchingItems.value = count
    emit('total', count)
  })

const hasSelection = computed(() => (isMultiple.value ? selectedItems.value.length > 0 : selectedItem.value !== null))

const setValidSelectedState = () => {
  valid.value = props.validIconOnSelected ? true : null
}

watch(
  () => props.valid,
  (newValue) => {
    valid.value = newValue ?? null
  },
)

const componentRef = ref<HTMLElement | null>(null)
const dropdownRef = ref<HTMLElement | null>(null)
const inputComponentRef = ref<InstanceType<typeof FdsInput> | null>(null)
const inputId = `searchSelectInput-${Math.random().toString(36).slice(2, 9)}`
const listboxId = `select-dropdown-${Math.random().toString(36).slice(2, 9)}`

// Helper function to get the actual input element
const getInputElement = (): HTMLInputElement | null => {
  // Try to get from component ref first
  if (inputComponentRef.value?.$el) {
    const input = inputComponentRef.value.$el.querySelector('input') as HTMLInputElement
    if (input) return input
  }
  // Fallback to getElementById
  return document.getElementById(inputId) as HTMLInputElement | null
}

// Helper functions
const focusInput = () => {
  nextTick(() => {
    const input = getInputElement()
    input?.focus()
  })
}

const handleBlurInput = () => {
  const input = getInputElement()
  input?.blur()
}

const handleBlurAndClear = () => {
  const input = getInputElement()
  input?.blur()
  searchTerm.value = ''
  selectedItem.value = null
  selectedItems.value = []
  showSelectedOnly.value = false
  valid.value = null
}

const uncheckAllRadios = () => {
  if (!dropdownRef.value) return
  const checkedRadio = dropdownRef.value.querySelector(
    'input[name="search-select-radio-group"]:checked',
  ) as HTMLInputElement
  if (checkedRadio) {
    checkedRadio.checked = false
  }
}

const getSelectableInputs = (): HTMLInputElement[] => {
  if (!dropdownRef.value) return []
  return Array.from(dropdownRef.value.querySelectorAll('input[id^="search-select-radio-"]')) as HTMLInputElement[]
}

const focusSelectedRadioOrFirst = () => {
  nextTick(() => {
    const selectableInputs = getSelectableInputs()
    if (!selectableInputs.length) return

    const checkedRadio = selectableInputs.find((input) => input.checked)
    ;(checkedRadio ?? selectableInputs[0])?.focus()
  })
}

const hasInputValue = computed(() => searchTerm.value.length > 0)
const resolvedLoadMoreLabel = computed(() => t('FdsSearchSelectPro.showMore'))
const resolvedLoadingMoreLabel = computed(() => t('FdsSearchSelectPro.loadingMore'))
const displayInputValue = computed(() => {
  if (!inputHasFocus.value && !hasSelection.value && !searchTerm.value.length && props.displayValue) {
    return props.displayValue
  }
  if (isMultiple.value && !inputHasFocus.value && selectedItems.value.length > 0) {
    return `${selectedItems.value.length} val`
  }
  return searchTerm.value
})

const listWrapperClasses = computed(() => [
  'rounded-md box-border overflow-hidden z-4 w-full bg-white mt-2',
  'mb-8',
  'border border-gray-500',
  props.dropdownAbsolute ? 'absolute right-0 z-50 shadow-[0_12px_24px_0_rgba(12,72,153,0.12)]' : 'relative shadow-none',
  props.maxListHeight && 'overflow-y-auto',
])

const listItemClasses = computed(() => [
  'block m-0',
  'outline-none! hover:bg-blue_t-100 active:bg-blue_t-200',
  'border-b border-gray-100',
])

const listItemWrapperClasses = computed(() => [
  'block m-0 hover:outline-none',
  'has-focus-visible:outline-2 has-focus-visible:outline-blue-500 has-focus-visible:-outline-offset-2 has-focus-visible:outline-dashed',
])

const labelClasses = computed(() => 'block p-3 text-gray-700 cursor-pointer no-underline')

const headerClasses = computed(() => ['block m-0 border-b border-gray-100 rounded-t-md p-3'])

const listWrapperStyle = computed(() => {
  if (!props.maxListHeight) return null

  // Convert to number in case it's passed as a string
  const maxHeight = typeof props.maxListHeight === 'string' ? parseInt(props.maxListHeight, 10) : props.maxListHeight

  return { maxHeight: `${maxHeight}px` }
})

const handleClear = () => {
  bypassSearchFilter.value = false
  searchTerm.value = ''
  selectedItem.value = null
  selectedItems.value = []
  showSelectedOnly.value = false
  resetVisibleItemsLimit()
  valid.value = null
  emit('searchSelected', isMultiple.value ? [] : null)
  emit('clearInput', new Event('clearInput'))
  filterAndPaginate()
  focusInput()
}

watch(
  () => props.maxItems,
  () => {
    resetVisibleItemsLimit()
    filterAndPaginate()
  },
)

watch(
  () => [props.items, props.page, props.autoDividerBy, props.searchFields],
  () => {
    if (isMultiple.value) {
      syncSelectedItemsWithItems()
    }
    filterAndPaginate()
    if (!selectedItem.value) {
      resolveInitialSingleSelection()
    }
  },
  { immediate: true },
)

const isDebouncingChange = ref(false)
const isSearching = computed(() => isDebouncingChange.value || props.loading)

const debouncedEmitChange = debounce(
  (value: string) => {
    emit('change', formatPidWithDash(value))
    isDebouncingChange.value = false
  },
  () => Math.max(0, props.debounceMs ?? 500),
)

const debouncedEmitInput = debounce(
  (ev: Event) => {
    emit('input', ev)
  },
  () => Math.max(0, props.debounceMs ?? 500),
)

const scheduleDebouncedEmitChange = (value: string) => {
  isDebouncingChange.value = true
  debouncedEmitChange(value)
}

watch(
  () => searchTerm.value,
  (newValue) => {
    if (props.maxItems && props.maxItems > 0) {
      resetVisibleItemsLimit()
    }
    filterAndPaginate()
    if (isMultiple.value || selectedItem.value === null) {
      scheduleDebouncedEmitChange(newValue)
    }
  },
)

const onClickOutside = (e: PointerEvent) => {
  const target = e.target as Element
  if (componentRef.value?.contains(target)) return

  dropdownVisible.value = false
  if (isMultiple.value) {
    if (selectedItems.value.length > 0) {
      setValidSelectedState()
    } else if (props.invalidOnEmptySelection && searchTerm.value.length > 0) {
      valid.value = false
    } else {
      valid.value = null
    }
    return
  }

  if (props.initialValue === searchTerm.value && props.initialValue.length) {
    setValidSelectedState()
  } else if (searchTerm.value.length > 0 && selectedItem.value === null) {
    valid.value = false
  }
}

const handleInput = (e: Event) => {
  const target = e.target as HTMLInputElement
  const { value } = target

  bypassSearchFilter.value = false
  searchTerm.value = value
  dropdownVisible.value = true
  if (!isMultiple.value) {
    selectedItem.value = null
    valid.value = null
  }
  filterAndPaginate()
  debouncedEmitInput(e)
  if (!isMultiple.value) {
    uncheckAllRadios()
  }
}

// Bold query matches using composable
const { boldQuery } = useBoldQuery(searchTerm)

// Mask configuration for personnummer (yyyymmdd-nnnn)
const pidMask = computed(() => (isPid.value ? PID_MASK : undefined))

const pidMaskOptions = computed(() => (isPid.value ? PID_MASK_OPTIONS : { lazy: true }))

const handleMatchingString = (item: Record<string, unknown>): string => {
  if (isDividerItem(item)) {
    const dividerLabel =
      searchFields.value.map((key) => String(item[key] ?? '').trim()).find((value) => value.length > 0) ??
      String(item.label ?? item.name ?? '')

    if (props.highlightDividerMatch && searchTerm.value) {
      const formattedTerm = formatPidWithDash(searchTerm.value)
      const escaped = formattedTerm.replace(/[()]/g, '\\$&')
      if (new RegExp(escaped, 'i').test(dividerLabel)) {
        return boldQuery(dividerLabel)
      }
    }

    return dividerLabel
  }

  const values = searchFields.value.map((key) => String(item[key] ?? '').trim()).filter((value) => value.length > 0)

  let result = ''
  if (values.length === 0) {
    return ''
  }

  if (values.length === 1) {
    result = values[0] ?? ''
  } else {
    // Format PID if second value is 12 digits
    if (values[1] && /^\d{8}[a-zA-Z0-9]{4}$/.test(normalizePidSearchValue(values[1]))) {
      values[1] = formatPidWithDash(values[1])
    }

    if (props.preserveOrder) {
      // Combine first two values, then join remaining with <br>
      const primaryValue = values[0] ?? ''
      const secondaryValue = values[1]
      const combined = secondaryValue ? `${primaryValue} (${secondaryValue})` : primaryValue
      const rest = values.slice(2).join('<br>')
      result = rest ? `${combined}<br>${rest}` : combined
    } else {
      result = values[0] ?? ''
      for (let i = 1; i < values.length; i++) {
        const value = values[i]
        if (value) {
          result += ` (${value})`
        }
      }
    }
  }

  if (searchTerm.value) {
    const formattedTerm = formatPidWithDash(searchTerm.value)
    const escaped = formattedTerm.replace(/[()]/g, '\\$&')
    if (new RegExp(escaped, 'i').test(result)) {
      return boldQuery(result)
    }
  }

  return result
}

function selectItem(item: Record<string, unknown>) {
  selectedItem.value = item
  setValidSelectedState()
  emit('searchSelected', item)
}

function getCandidateMatchValues(item: Record<string, unknown>): string[] {
  if (isDividerItem(item) && !props.dividerSelectable) return []

  if (searchFields.value.length > 0) {
    return searchFields.value.map((key) => String(item[key] ?? '').trim()).filter((value) => value.length > 0)
  }

  return [String(item.label ?? item.name ?? '').trim()].filter((value) => value.length > 0)
}

function resolveInitialSingleSelection() {
  if (isMultiple.value || !props.initialValue) return

  const normalizeForInitialMatch = (value: string): string => {
    const trimmed = value.trim()
    if (!trimmed) return ''
    if (!isPidString(trimmed)) {
      return trimmed.replace(/-$/, '').toLowerCase()
    }
    return formatPidWithDash(trimmed).toLowerCase()
  }

  const normalizedInitialValue = normalizeForInitialMatch(props.initialValue)
  if (!normalizedInitialValue) return

  const initialMatch = sourceItems.value.find((item) =>
    getCandidateMatchValues(item).some((value) => normalizeForInitialMatch(value) === normalizedInitialValue),
  )

  if (!initialMatch) return

  const firstField = searchFields.value[0]
  searchTerm.value = (firstField ? String(initialMatch[firstField] ?? '') : props.initialValue).trim()
  selectItem(initialMatch)
}

const getHierarchyLevel = (item: Record<string, unknown>): number => {
  const level = Number(item[props.levelField] ?? 0)
  return Number.isFinite(level) && level >= 0 ? level : 0
}

const getRecursiveSelectionItems = (item: Record<string, unknown>): Array<Record<string, unknown>> => {
  if (isDividerItem(item)) {
    if (!props.dividerSelectable) return []
    if (!(props.selectChildrenRecursively && isMultiple.value)) {
      return [item]
    }

    const orderedItems = sourceItems.value
    const startIndex = orderedItems.findIndex((candidate) => getItemIdentifier(candidate) === getItemIdentifier(item))
    if (startIndex < 0) return [item]

    const selection: Array<Record<string, unknown>> = [item]
    for (let i = startIndex + 1; i < orderedItems.length; i++) {
      const candidate = orderedItems[i]
      if (!candidate) continue
      if (isDividerItem(candidate)) break
      selection.push(candidate)
    }
    return selection
  }

  if (!props.selectChildrenRecursively || !props.hierarchical) {
    return [item]
  }

  const orderedItems = sourceItems.value
  const startIndex = orderedItems.findIndex((candidate) => getItemIdentifier(candidate) === getItemIdentifier(item))
  if (startIndex < 0) return [item]

  const parentLevel = getHierarchyLevel(item)
  const selection: Array<Record<string, unknown>> = [item]

  for (let i = startIndex + 1; i < orderedItems.length; i++) {
    const candidate = orderedItems[i]
    if (!candidate) continue
    if (isDividerItem(candidate)) break

    const candidateLevel = getHierarchyLevel(candidate)
    if (candidateLevel <= parentLevel) break

    selection.push(candidate)
  }

  return selection
}

const toggleMultipleSelection = (item: Record<string, unknown>) => {
  const itemsToToggle = getRecursiveSelectionItems(item).filter(
    (candidate) => props.dividerSelectable || !isDividerItem(candidate),
  )
  if (!itemsToToggle.length) return
  const shouldDeselect = isItemSelected(item)

  if (shouldDeselect) {
    const idsToRemove = new Set(itemsToToggle.map((candidate) => getItemIdentifier(candidate)))
    selectedItems.value = selectedItems.value.filter((selected) => !idsToRemove.has(getItemIdentifier(selected)))
  } else {
    const existingIds = new Set(selectedItems.value.map((selected) => getItemIdentifier(selected)))
    itemsToToggle.forEach((candidate) => {
      const candidateId = getItemIdentifier(candidate)
      if (!existingIds.has(candidateId)) {
        selectedItems.value.push(candidate)
        existingIds.add(candidateId)
      }
    })
  }

  if (selectedItems.value.length > 0) {
    valid.value = props.validIconOnSelected ? true : null
  } else {
    valid.value = props.invalidOnEmptySelection ? false : null
  }
  emit('searchSelected', [...selectedItems.value])
}

const handleMultipleSelectionChange = (item: Record<string, unknown>) => {
  toggleMultipleSelection(item)
  filterAndPaginate()
}

const handleSingleSelection = (item: Record<string, unknown>) => {
  const firstField = searchFields.value[0]
  searchTerm.value = (firstField ? (item[firstField] as string) : '') || ''
  selectItem(item)
  dropdownVisible.value = false

  // Blur input after selection so next interaction starts from a clean focus state.
  setTimeout(() => {
    handleBlurInput()
  }, 100)
}

const handleClick = (e: MouseEvent | KeyboardEvent, item: Record<string, unknown>) => {
  if (isDividerItem(item) && !props.dividerSelectable) {
    return
  }

  const keyEvent = e as KeyboardEvent
  if (keyEvent.key === 'Enter' || keyEvent.code === 'Space' || e.type === 'mouseup') {
    // Prevent event from bubbling and triggering other handlers
    e.preventDefault()
    e.stopPropagation()

    if (isMultiple.value) {
      toggleMultipleSelection(item)
      filterAndPaginate()
      return
    }

    handleSingleSelection(item)
  }
}

const handleToggleSelectedOnly = () => {
  showSelectedOnly.value = !showSelectedOnly.value
  if (props.maxItems && props.maxItems > 0) {
    resetVisibleItemsLimit()
  }
  filterAndPaginate()
}

const handleLoadMore = () => {
  if (props.loadingMore) return
  if (handleInternalLoadMore()) {
    filterAndPaginate()
    return
  }
  emit('loadMore')
}

const handleDropdownScroll = (event: Event) => {
  if (!props.autoLoadOnScroll || props.loadingMore || !shouldShowLoadMore.value) return

  const target = event.target as HTMLElement | null
  if (!target) return

  const threshold = Math.max(0, props.autoLoadThreshold ?? 24)
  const remaining = target.scrollHeight - (target.scrollTop + target.clientHeight)
  if (remaining <= threshold) {
    handleLoadMore()
  }
}

const handleInputFocus = () => {
  inputHasFocus.value = true
  if (!isMultiple.value && !!selectedItem.value) {
    bypassSearchFilter.value = true
    filterAndPaginate()
  }
  // Open dropdown on focus whenever there are items to show.
  if (displayedItems.value.length > 0) {
    dropdownVisible.value = true
  }
}

const handleInputBlur = () => {
  inputHasFocus.value = false
  bypassSearchFilter.value = false
}

const handleListKeyDown = (e: KeyboardEvent) => {
  const target = e.target as HTMLElement | null
  const selectableInputs = getSelectableInputs()
  const targetInput = target instanceof HTMLInputElement ? target : null
  const currentIndex = targetInput ? selectableInputs.indexOf(targetInput) : -1

  // Handle Escape key to close dropdown
  if (e.key === 'Escape') {
    e.preventDefault()
    dropdownVisible.value = false
    uncheckAllRadios()
    focusInput()
  }

  if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && currentIndex >= 0) {
    e.preventDefault()
    const direction = e.key === 'ArrowDown' ? 1 : -1
    const nextIndex = currentIndex + direction
    if (nextIndex >= 0 && nextIndex < selectableInputs.length) {
      selectableInputs[nextIndex]?.focus()
    }
    return
  }

  if (isMultiple.value && e.key === 'Enter') {
    if (targetInput) {
      e.preventDefault()
      targetInput.click()
    }
    return
  }

  if (!isMultiple.value && e.key === 'Enter' && targetInput) {
    const match = targetInput.id.match(/search-select-radio-(\d+)/)
    const index = match ? Number(match[1]) : -1
    const item = index >= 0 ? displayedItems.value[index] : undefined
    if (item) {
      e.preventDefault()
      handleSingleSelection(item)
    }
    return
  }

  if (targetInput && (e.key === ' ' || e.code === 'Space')) {
    e.preventDefault()
    return
  }

  if (e.key === 'Tab') {
    dropdownVisible.value = false
    uncheckAllRadios()
  }
  // Arrow keys are handled natively by radio buttons
  // Enter/Space are handled by handleClick on the radio button change event
}

const handleInputKeyDown = (e: KeyboardEvent) => {
  // Handle ArrowDown to open dropdown and focus first radio button
  if (e.key === 'ArrowDown' && displayedItems.value.length > 0) {
    e.preventDefault()
    if (!dropdownVisible.value) {
      dropdownVisible.value = true
    }
    focusSelectedRadioOrFirst()
    return
  }

  // Handle Enter to open dropdown if closed
  if (e.key === 'Enter' && !dropdownVisible.value && displayedItems.value.length > 0) {
    e.preventDefault()
    dropdownVisible.value = true
    focusSelectedRadioOrFirst()
    return
  }

  // Handle Escape to close dropdown
  if (e.key === 'Escape' && dropdownVisible.value) {
    e.preventDefault()
    dropdownVisible.value = false
    uncheckAllRadios()
    focusInput()
  }

  // Tab should move to next element and close list instead of entering options.
  if (e.key === 'Tab' && dropdownVisible.value) {
    dropdownVisible.value = false
    uncheckAllRadios()
  }
}

onMounted(() => {
  if (props.initialValue) {
    searchTerm.value = props.initialValue
    resolveInitialSingleSelection()
    if (!selectedItem.value) {
      setValidSelectedState()
    }
  }

  window.addEventListener('mouseup', onClickOutside as EventListener)

  nextTick(() => {
    const input = getInputElement()
    if (input) {
      input.addEventListener('keydown', handleInputKeyDown)
      input.addEventListener('focus', handleInputFocus)
      input.addEventListener('blur', handleInputBlur)
    }
  })

  filterAndPaginate()
})

onBeforeUnmount(() => {
  window.removeEventListener('mouseup', onClickOutside as EventListener)
  debouncedEmitChange.cancel()
  debouncedEmitInput.cancel()
  isDebouncingChange.value = false
  const input = getInputElement()
  if (input) {
    input.removeEventListener('keydown', handleInputKeyDown)
    input.removeEventListener('focus', handleInputFocus)
    input.removeEventListener('blur', handleInputBlur)
  }
})

const rootAttrs = computed(() => {
  const attrs: Record<string, string> = {}
  if (props.dataTestid) {
    attrs['data-testid'] = props.dataTestid
  }
  return attrs
})

// Computed property to find the currently checked radio button ID for aria-activedescendant
const activeDescendantId = computed(() => {
  if (!dropdownVisible.value || !dropdownRef.value) return null
  const checkedInput = dropdownRef.value.querySelector(
    'input[name="search-select-radio-group"]:checked',
  ) as HTMLInputElement | null
  return checkedInput?.id || null
})

// ARIA attributes for input (combobox pattern)
const inputAriaAttrs = computed(() => {
  const attrs: Record<string, string | boolean> = {
    role: 'combobox',
    'aria-expanded': dropdownVisible.value,
    'aria-autocomplete': 'list',
    'aria-haspopup': 'listbox',
  }

  if (dropdownVisible.value && listboxId) {
    attrs['aria-controls'] = listboxId
  }

  if (activeDescendantId.value) {
    attrs['aria-activedescendant'] = activeDescendantId.value
  }

  return attrs
})

defineExpose<{
  blur: () => void
  clear: () => void
  blurAndClear: () => void
  focus: () => void
}>({
  blur: handleBlurInput,
  clear: handleClear,
  blurAndClear: handleBlurAndClear,
  focus: focusInput,
})
</script>

<template>
  <div ref="componentRef" class="fds-search-select block mb-6" v-bind="rootAttrs">
    <div class="relative block">
      <div class="relative">
        <div class="relative">
          <FdsInput
            ref="inputComponentRef"
            :label="label"
            :meta="meta ?? undefined"
            :labelLeft="labelLeft"
            class="relative mb-0! w-full!"
            :valid="valid"
            :invalidMessage="invalidMessage"
            :disabled="disabled"
            type="search"
            :value="displayInputValue"
            :id="inputId"
            :clearButton="!!searchTerm || hasSelection"
            :mask="pidMask"
            :maskOptions="pidMaskOptions"
            v-bind="inputAriaAttrs"
            :searchIcon="!disabled && !inputHasFocus && !hasInputValue && !hasSelection && !dropdownVisible"
            @input="handleInput"
            @clearInput="handleClear"
          />
        </div>
      </div>

      <!-- Dropdown List -->
      <div
        ref="dropdownRef"
        v-if="dropdownVisible && !disabled"
        :class="listWrapperClasses"
        :style="listWrapperStyle"
        @scroll.passive="handleDropdownScroll"
      >
        <!-- Results -->
        <template v-if="displayedItems && displayedItems.length">
          <!-- Header -->
          <div :class="[headerClasses, 'flex items-center justify-between gap-3 min-h-[53px]']">
            <div class="flex items-center gap-2">
              <FdsMeta>
                {{
                  searchTerm && searchTerm.length
                    ? `${totalMatchingItems} ${searchContext.linkWord} ${totalCount} ${searchContext.context}`
                    : `${totalCount} ${searchContext.context}`
                }}
              </FdsMeta>
              <FdsSpinner v-if="isSearching" color="blue" size="20px" />
            </div>

            <FdsCheckbox
              v-if="multiple && showSelectedToggle && selectedItems.length > 0"
              :label="`${selectedToggleLabel} (${selectedItems.length})`"
              :checked="showSelectedOnly"
              @change="handleToggleSelectedOnly"
            />
          </div>

          <!-- List -->
          <ul class="block m-0 list-none p-0" role="listbox" :id="listboxId" @keydown="handleListKeyDown">
            <li
              v-for="(item, index) in displayedItems"
              :key="`${index}-${getItemIdentifier(item)}`"
              :id="`search-select-item-${index}`"
              :class="listItemWrapperClasses"
              :role="isDividerItem(item) && !dividerSelectable ? 'presentation' : 'option'"
              @mouseup="(e) => !multiple && handleClick(e, item)"
            >
              <div
                v-if="isDividerItem(item) && !dividerSelectable"
                :class="[
                  labelClasses,
                  'font-normal text-gray-700 border-b border-gray-100 cursor-default pointer-events-none',
                ]"
                :style="optionPaddingStyle(item)"
                v-html="handleMatchingString(item)"
              ></div>
              <div
                v-else
                @click="() => handleMultipleSelectionChange(item)"
                :class="[labelClasses, listItemClasses]"
                :style="optionPaddingStyle(item)"
              >
                <FdsCheckbox
                  v-if="multiple"
                  :id="`search-select-radio-${index}`"
                  :name="`search-select-checkbox-group-${inputId}`"
                  :checked="isItemSelected(item)"
                  :tabindex="-1"
                  :disablePseudoStates="true"
                  class="mb-0!"
                  @change="() => handleMultipleSelectionChange(item)"
                >
                  <span class="min-w-0 flex-1" v-html="handleMatchingString(item)" />
                </FdsCheckbox>
                <FdsRadio
                  v-else
                  :id="`search-select-radio-${index}`"
                  name="search-select-radio-group"
                  :value="index"
                  :checked="!!selectedItem && getItemIdentifier(selectedItem) === getItemIdentifier(item)"
                  :tabindex="-1"
                  :disablePseudoStates="true"
                  class="mb-0!"
                  @change="() => handleSingleSelection(item)"
                  @keydown.enter="(e) => handleClick(e, item)"
                >
                  <span class="min-w-0 flex-1" v-html="handleMatchingString(item)" />
                </FdsRadio>
              </div>
            </li>
            <li v-if="shouldShowLoadMore" class="border-b border-blue_t-200 p-3">
              <div
                class="flex items-center gap-2 text-blue-600"
                :class="{ 'cursor-pointer': !loadingMore, 'cursor-not-allowed opacity-70': loadingMore }"
                role="button"
                tabindex="-1"
                :aria-disabled="loadingMore"
                @click="!loadingMore && handleLoadMore()"
                @keydown.enter.prevent="!loadingMore && handleLoadMore()"
                @keydown.space.prevent="!loadingMore && handleLoadMore()"
              >
                <FdsButtonIcon
                  icon="sync"
                  :loading="loadingMore"
                  :aria-label="loadingMore ? resolvedLoadingMoreLabel : resolvedLoadMoreLabel"
                  :size="24"
                  class="w-6 h-6 pointer-events-none"
                />
                <span>{{ loadingMore ? resolvedLoadingMoreLabel : resolvedLoadMoreLabel }}</span>
              </div>
            </li>
          </ul>
        </template>

        <!-- No Results -->
        <ul v-else class="block m-0 list-none p-0">
          <li class="p-3 flex items-center gap-2">
            <span v-if="!isSearching">{{ noResultPrompt }}</span>
            <FdsSpinner v-if="isSearching" color="blue" size="20px" />
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>
