// TODO: Update DropdownProps["value"] so the type matches SelectHTMLAttributes["value"]
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState, useEffect, useCallback, ReactNode } from 'react'

import { nanoid } from 'nanoid'
import classNames from 'classnames'
import selfless from '@komi-app/selfless'
import When from '@komi-app/when'

import styles from './dropdown.module.scss'
import { DropdownMenu } from './menu'
import { DropdownField } from './field'

import {
  MdKeyboardArrowDown as KeyboardArrowDown,
  MdKeyboardArrowUp as KeyboardArrowUp
} from 'react-icons/md'
import { useOutsideClick, TagProps } from '@komi-app/creator-ui'

export type DropdownOption = {
  value: any
  label?: string
  disabled?: boolean
  prefixIcon?: ReactNode
  suffixIcon?: ReactNode
  tagProps?: TagProps
}

export type DropdownItem = string | DropdownOption

export type DropdownProps = Omit<
  React.SelectHTMLAttributes<HTMLSelectElement>,
  'onSelect' | 'onChange'
> & {
  label?: string
  name?: string
  helper?: string
  hasError?: boolean
  noLabel?: boolean
  noHelper?: boolean
  keyword?: string
  placeholder: React.ReactNode
  hasSearch?: boolean
  searchPlaceholder?: string
  items?: DropdownItem[]
  heading?: React.ReactNode

  onClick: (e: React.MouseEvent) => void
  onSelect: (selectedItems: DropdownItem[]) => void
  onChange: (value: any) => void
  onToggle: (expanded: boolean) => void
}

export interface DropdownViewProps
  extends Omit<
    DropdownProps,
    'onClick' | 'onSelect' | 'onChange' | 'noLabel' | 'noHelper'
  > {
  name: string
  ref: React.MutableRefObject<any>
  containerClassName: string
  labelClassName: string
  helperClassName: string
  expanded: boolean
  current?: DropdownItem
  selectedItems: DropdownItem[]
  showLabel: boolean
  showHelper: boolean
  dropdownWrapperClass: string

  click: (e: React.MouseEvent) => void
  change: (value: DropdownItem, remove?: boolean) => void
  keyUp: (e: React.KeyboardEvent) => void
}

export function itemValue(item: DropdownItem) {
  return typeof item === 'string' ? item : item.value
}

export function itemText(item: DropdownItem) {
  return typeof item === 'string' ? item : item.label || item.value
}

function selectedFilter(value: any | any[]) {
  return Array.isArray(value)
    ? (item: DropdownItem) =>
        typeof item === 'string'
          ? value.includes(item)
          : value.includes(item.value)
    : (item: DropdownItem) =>
        typeof item === 'string' ? item === value : item.value === value
}

export function useDropdown({
  hasError,
  name: moniker,
  multiple,
  value: originalValue,
  items = [],
  noHelper,
  noLabel,
  onClick,
  onChange,
  onSelect,
  onToggle,
  ...props
}: DropdownProps): DropdownViewProps {
  // generate a unique name for each menu
  // to manage radio/checkbox group
  const [name] = useState(moniker || nanoid())
  const [expanded, setExpanded] = useState(false)
  const [value, setValue] = useState(originalValue)
  const [current, setCurrent] = useState<DropdownItem>()
  const [selectedItems, setSelectedItems] = useState<DropdownItem[]>(
    items.filter(selectedFilter(originalValue))
  )
  const ref = useOutsideClick(() => {
    onToggle(false)
    setExpanded(false)
  })

  useEffect(() => {
    // setSelectedItems(items.filter(selectedFilter(value)));
  }, [value, current, items])

  // TODO: dropdown position computation

  const toggle = useCallback(() => {
    onToggle(!expanded)
    setExpanded(!expanded)
  }, [expanded])

  const change = useCallback(
    (item: DropdownItem, remove?: boolean) => {
      setValue(value => {
        let patch
        const newValue = itemValue(item)

        if (multiple) {
          const array = Array.isArray(value) ? value : value ? [value] : []

          patch = remove
            ? array.filter(value => value !== newValue)
            : array.concat([newValue])
        } else if (!remove) {
          patch = newValue
        }

        onChange && onChange(patch)
        onSelect && onSelect(items.filter(selectedFilter(patch)))
        if (!multiple) setExpanded(false)

        return patch
      })
    },
    [multiple, onChange, onSelect, items]
  )

  const selectCurrent = useCallback(() => {
    if (!current) return

    const remove = selectedItems.includes(current)
    change(current, remove)

    const selection = remove
      ? selectedItems.filter(item => item !== current)
      : multiple
        ? selectedItems.concat([current])
        : [current]

    setSelectedItems(selection)
  }, [change, current, multiple, selectedItems])

  const click = useCallback(
    (e: React.MouseEvent) => {
      if (expanded) {
        selectCurrent()
        setCurrent(void 0)
      }

      toggle()

      onClick && onClick(e)
    },
    [expanded, toggle, onClick, selectCurrent]
  )

  const keyUp = useCallback(
    (e: React.KeyboardEvent) => {
      let index

      switch (e.code) {
        case 'Escape':
          return setExpanded(false)

        case 'Enter':
        case 'Space':
          expanded && selectCurrent()
          // those keys trigger a click event which closes the dropdown
          return

        case 'ArrowDown':
          index = (current ? items.indexOf(current) + 1 : 0) % items.length
          setCurrent(items[index])
          break

        case 'ArrowUp':
          index = (current ? items.indexOf(current) - 1 : 0) % items.length

          index = index < 0 ? items.length - 1 : index

          setCurrent(items[index])
          break

        default:
          return
      }

      if (!expanded) toggle()
    },
    [current, expanded, items, selectCurrent, toggle]
  )

  return {
    name,
    ref,
    expanded,
    value,
    multiple,
    current,
    items,
    selectedItems,
    showHelper: !noHelper,
    showLabel: !noLabel,
    click,
    change,
    keyUp,
    onToggle,
    containerClassName: classNames([styles.container]),
    labelClassName: styles.label,
    dropdownWrapperClass: styles.dropdownWrapper,
    helperClassName: classNames(styles.helper, {
      [styles.helper__error]: hasError
    }),
    hasError,
    ...props
  }
}

export const DropdownView = ({
  label,
  ref,
  current,
  showLabel,
  showHelper,
  name,
  helper,
  disabled,
  expanded,
  containerClassName,
  labelClassName,
  dropdownWrapperClass,
  keyword,
  helperClassName,
  placeholder,
  hasSearch,
  searchPlaceholder,
  multiple,
  items = [],
  value,
  hasError,
  selectedItems,
  children,
  heading,
  click,
  change,
  keyUp
}: DropdownViewProps) => (
  <div ref={ref} className={containerClassName}>
    <When
      value={showLabel}
      then={<span className={labelClassName}>{label}</span>}
    />

    <When
      value={heading}
      then={
        <button
          disabled={disabled}
          className={classNames(styles.headingButton)}
          onClick={click}
          type="button"
        >
          {heading}
        </button>
      }
      otherwise={
        <DropdownField
          keyword={keyword}
          placeholder={String(placeholder)}
          disabled={disabled}
          expanded={expanded}
          value={value}
          onClick={click}
          onKeyUp={keyUp}
          hasError={hasError}
          selectedItems={selectedItems}
          suffixIcon={
            expanded ? (
              <KeyboardArrowUp size={24} />
            ) : (
              <KeyboardArrowDown size={24} />
            )
          }
        />
      }
    />

    {expanded && (
      <div className={dropdownWrapperClass}>
        <When
          value={children}
          then={<div>{children}</div>}
          otherwise={
            <DropdownMenu
              multiple={multiple}
              name={name}
              onChange={change}
              onKeyUp={keyUp}
              current={current}
              items={items}
              selectedItems={selectedItems}
              hasSearch={hasSearch}
              searchPlaceholder={searchPlaceholder}
            />
          }
        />
      </div>
    )}

    <When
      value={showHelper}
      then={<span className={helperClassName}>{helper}</span>}
    />
  </div>
)

export const Dropdown = selfless(DropdownView, useDropdown)
export default Dropdown
