import { Checkbox, IconBox, Input, Text } from '@citric/core'
import { ChevronDown } from '@citric/icons'
import { LoadingCircular } from '@citric/ui'
import { listToClass } from '@stack-spot/portal-theme'
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
import { intersection, uniq } from 'lodash'
import { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { FieldComponent } from '../../form/types'

interface Props {
  options: string[],
  isLoading?: boolean,
  renderLabel?: (option: string) => React.ReactNode,
  actions?: { label: string, element?: React.ReactNode, onSelect: (e: SyntheticEvent<any>) => void }[],
  showMissingValues?: boolean,
}

export const RawMultiselectField: FieldComponent<string[], Props> = (
  { name, onChange, value, disabled, onBlur, options, isLoading = false, renderLabel, actions, showMissingValues },
) => {
  const t = useTranslate(dictionary)
  const [isOpen, setOpen] = useState(false)
  const ref = useRef<HTMLDivElement>(null)

  const items = useMemo(() => (showMissingValues && value?.length ? uniq([...options, ...value]) : options).map(o => (
    <li key={o}>
      <label tabIndex={0} onKeyDown={e => ['Enter', ' '].includes(e.key) && onToggleItem(o)}>
        <Checkbox tabIndex={-1} checked={value?.includes(o)} onChange={() => onToggleItem(o)} />
        {renderLabel ? renderLabel(o) : <Text>{o}</Text>}
      </label>
    </li>
  )), [JSON.stringify(options), value])

  const actionItems = useMemo(() => actions?.map(a => (
    <li key={a.label}>
      <label
        tabIndex={0}
        onKeyDown={(e) => {
          if (['Enter', ' '].includes(e.key)) {
            a.onSelect(e)
            close()
          }
        }}
        onClick={(e) => {
          a.onSelect(e)
          close()
        }}
        aria-label={a.label}
      >
        {a.element ?? <Text>{a.label}</Text>}
      </label>
    </li>
  )), [actions])

  useEffect(() => {
    if (!showMissingValues) onChange(intersection(value, options))
  }, [JSON.stringify(options)])

  const close = useCallback(() => {
    setOpen(false)
    onBlur?.()
    document.removeEventListener('keydown', keyboardController)
    document.removeEventListener('click', mouseController)
  }, [])

  const keyboardController = useCallback((event: KeyboardEvent) => {
    if (event.key === 'Escape') {
      close()
      ref.current?.querySelector('input')?.focus()
    }
    if (event.key === 'ArrowDown' || event.key === 'Tab') {
      const li = document.activeElement?.closest('li')
      if (!li?.nextElementSibling) {
        ref.current?.querySelector('input')?.focus()
        return close()
      }
      (li.nextElementSibling.querySelector('label') as HTMLElement | undefined)?.focus?.()
      event.preventDefault()
    }
    if (event.key === 'ArrowUp') {
      const li = document.activeElement?.closest('li')
      if (!li?.previousElementSibling) {
        ref.current?.querySelector('input')?.focus()
        return close()
      }
      (li.previousElementSibling.querySelector('label') as HTMLElement | undefined)?.focus?.()
    }
  }, [])

  const mouseController = useCallback((event: MouseEvent) => {
    if (!ref.current?.querySelector('.option-box')?.contains(event.target as HTMLElement)) close() 
  }, [])

  const open = useCallback(() => {
    if (disabled) return
    setOpen(true)
    document.addEventListener('keydown', keyboardController)
    document.addEventListener('click', mouseController)
  }, [disabled])

  const onInputKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
    if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
      open()
      e.preventDefault()
      e.stopPropagation()
      // we can't wait for React's next re-render to remove the attribute "inert", it would make it impossible to focus the first item.
      ref.current?.querySelector('.option-box')?.removeAttribute('inert')
      const firstCheckbox = ref.current?.querySelector('li label') as HTMLElement | undefined
      firstCheckbox?.focus()
    }
    if (e.key !== 'Tab') e.preventDefault()
  }, [])

  const onToggleItem = useCallback((option: string) => {
    if (value?.includes(option)) onChange(value.filter(v => v != option))
    else onChange([...(value ?? []), option])
  }, [value])

  return (
    <div ref={ref} className={listToClass(['multiselect', isOpen && 'open'])}>
      <Input
        aria-label={t.help}
        name={name}
        value={value?.join(', ')}
        onClick={(e) => {
          e.stopPropagation()
          isOpen ? close() : open()
        }}
        onKeyDown={onInputKeyDown}
        disabled={disabled || isLoading}
        autoComplete="false"
        autoCorrect="false"
      />
      <IconBox className="chevron"><ChevronDown /></IconBox>
      {isLoading && <div className="loading"><LoadingCircular size="sm" /></div>}
      {!disabled && !isLoading && <div className="option-box" {...(isOpen ? {} : { inert: '' })}><ul>{items}{actionItems}</ul></div>}
    </div>
  )
}

const dictionary = {
  en: {
    help: 'Press enter to choose multiple values in the list.',
  },
  pt: {
    help: 'Pressione enter para escolher múltiplos valores na lista.',
  },
} satisfies Dictionary
