import { Select, Spin } from 'antd'
import type { SelectProps } from 'antd/es/select'
import debounce from 'lodash/debounce'
import React, { useMemo, useRef, useState } from 'react'

export interface DebounceSelectProps<ValueType = any>
  extends Omit<SelectProps<ValueType | ValueType[]>, 'options' | 'children'> {
  fetchOptions: (search: string) => Promise<ValueType[]>
  debounceTimeout?: number
  normalizeSearch?: (search: string) => string
}

function DebounceSelect<
  ValueType extends {
    key?: string
    label: React.ReactNode
    value: string | number
  } = any
>({
  fetchOptions,
  debounceTimeout = 300,
  normalizeSearch,
  onSearch,
  ...props
}: DebounceSelectProps<ValueType>) {
  const [fetching, setFetching] = useState(false)
  const [options, setOptions] = useState<ValueType[]>([])
  const fetchRef = useRef(0)

  const debounceFetcher = useMemo(() => {
    const loadOptions = (value: string) => {
      fetchRef.current += 1
      const fetchId = fetchRef.current
      setOptions([])
      setFetching(true)

      fetchOptions(value).then((newOptions) => {
        if (fetchId !== fetchRef.current) {
          // for fetch callback order
          return
        }

        setOptions(newOptions)
        setFetching(false)
      })
    }

    return debounce(loadOptions, debounceTimeout)
  }, [fetchOptions, debounceTimeout])

  const handleSearch = (value: string) => {
    const normalizedValue = normalizeSearch ? normalizeSearch(value) : value

    onSearch?.(normalizedValue)
    debounceFetcher(normalizedValue)
  }

  return (
    <Select
      labelInValue
      filterOption={false}
      notFoundContent={fetching ? <Spin size="small" /> : null}
      {...props}
      onSearch={handleSearch}
      options={options}
    />
  )
}

export default DebounceSelect
