import type {
  FocusEvent,
  ChangeEvent,
  KeyboardEvent,
  BaseSyntheticEvent,
} from 'react'
import {
  useState,
  useCallback,
  useRef,
  useEffect,
  useMemo,
} from 'react'

import toLower from 'lodash/toLower'
import find from 'lodash/find'
import trim from 'lodash/trim'
import size from 'lodash/size'
import findIndex from 'lodash/findIndex'

import { useToggle } from 'hooks'

import type { Props, Option } from '../types'
import { matchSort } from '../helpers'
import { useKeyboardScroll } from './useKeyboardScroll'

const isOptionClicked = (target: HTMLElement | null) => (
  target?.getAttribute('role') === 'option'
)

export const useCombobox = <T extends Option>({
  noSearch,
  onBlur,
  onChange,
  onSelect,
  options,
  selected,
  value: query,
}: Props<T>) => {
  const inputFieldRef = useRef<HTMLInputElement>(null)
  const [index, setIndex] = useState(0)

  const {
    onKeyDownScroll,
    popoverRef,
  } = useKeyboardScroll()

  const {
    close,
    isOpen,
    open,
    toggle,
  } = useToggle()

  const filteredOptions = useMemo(() => (selected && noSearch ? options : matchSort(
    options,
    'name',
    query,
  )), [
    options,
    query,
    selected,
    noSearch,
  ])

  const findOptionByName = useCallback((optionName: string) => (
    find(
      options,
      ({ name }) => toLower(name) === toLower(trim(optionName)),
    ) || null
  ), [options])

  const onQueryChange = (e: ChangeEvent<HTMLInputElement>) => {
    onChange?.(e)
    if (selected && onSelect) {
      onSelect(null)
    }
  }

  const onOptionSelect = useCallback((option: string, e?: BaseSyntheticEvent) => {
    e?.stopPropagation()
    const selectedOption = findOptionByName(option)
    onSelect?.(selectedOption)
    close()
  }, [
    close,
    findOptionByName,
    onSelect,
  ])

  const onOutsideClick = (event?: MouseEvent) => {
    if (event?.target !== inputFieldRef.current) {
      close()
    }
  }

  const onInputBlur = (event: FocusEvent<HTMLInputElement>) => {
    const target = event.relatedTarget as HTMLElement | null
    // клик по элементу списка тоже вызывает onBlur
    // если кликали элемент списка то событие обрабатывает onOptionSelect

    if (isOptionClicked(target)) return

    onBlur?.(event)
  }

  useEffect(() => {
    const selectedIndex = findIndex(filteredOptions, ({ name }) => name === query)
    setIndex(selectedIndex)
  }, [query, filteredOptions])

  const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) => {
    onKeyDownScroll(event)
    if (event.key === 'Escape') {
      close()
      inputFieldRef.current?.blur()
    } else if (event.key === 'ArrowUp') {
      setIndex(index < 0 ? 0 : index - 1)
    } else if (event.key === 'ArrowDown') {
      const lastElementIndex = size(filteredOptions) - 1
      setIndex(index >= lastElementIndex ? lastElementIndex : index + 1)
    } else if (event.key === 'Enter') {
      onSelect?.(filteredOptions[index])
      close()
      inputFieldRef.current?.blur()
    }
  }, [
    close,
    index,
    filteredOptions,
    onSelect,
    onKeyDownScroll,
  ])

  return {
    close,
    index,
    inputFieldRef,
    isOpen,
    onInputBlur,
    onKeyDown,
    onOptionSelect,
    onOutsideClick,
    onQueryChange,
    open,
    options: filteredOptions,
    popoverRef,
    query,
    toggle,
  }
}
