import { Children, isValidElement, useState } from 'react'

import { ChangeEvent, toggleItem, useCallbackRef } from '../utils'
import { SelectOptionData, SelectValue } from './select.types'
import { UseSelectProps } from './useSelect.types'

export const getOptionText = (value: SelectValue, options: SelectOptionData[]) => {
  const option = options?.find((o: SelectOptionData) => o?.value === value)

  return option?.text || value
}

/** Handles controlled/uncontrolled value state. Supports single and multiple values. */
export default function useSelect(props: UseSelectProps) {
  const {
    defaultValue,
    isMultiple,
    onChange: onChangeProp,
    value: valueProp,
    options = [],
    selectedText,
    children,
  } = props

  // Not using useControlled here, because we need custom value setter when uncontrolled
  const [stateValue, setStateValue] = useState(defaultValue)
  const isControlled = valueProp !== undefined
  const value = isControlled ? valueProp : stateValue

  // Iterating over the children to dynamically obtain the list of available options
  const dynamicOptions: SelectOptionData[] = []
  Children.forEach(children, element => {
    if (!isValidElement(element)) return
    const option: SelectOptionData = element.props as SelectOptionData
    if (option.text && option.value) dynamicOptions.push(option)
  })

  /** Sends a change event to the external consumer, but internall keeps the actual value as primitive or array. */
  const onChange = useCallbackRef((e: ChangeEvent) => {
    onChangeProp?.(e)

    if (!isControlled)
      setStateValue(value => (Array.isArray(value) ? toggleItem(value, e.target.value).sort() : e.target.value))
  })

  const valueText =
    isMultiple && Array.isArray(value)
      ? value[0]
        ? selectedText && value.length > 1
          ? selectedText
          : `${getOptionText(value[0], [...options, ...dynamicOptions])}${
              value.length > 1 ? ` (+${value.length - 1})` : ''
            }`
        : undefined
      : getOptionText(value as SelectValue, [...options, ...dynamicOptions])

  return { onChange, value, valueText }
}

export type UseSelectReturnType = ReturnType<typeof useSelect>
