import React from 'react'
import PropTypes from 'prop-types'
import { View, Platform, StyleSheet } from 'react-native'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { withLinkRouter } from '../utils/withLinkRouter'
import { getTokensPropType } from '../utils/props/tokens'
import { variantProp } from '../utils/props/variantProp'
import { ExpandCollapse } from '../ExpandCollapse/ExpandCollapse'
import { ListboxGroup } from './ListboxGroup'
import { ListboxItem } from './ListboxItem'
import { ListboxContext } from './ListboxContext'
import { dictionary as defaultDictionary } from './dictionary'

const styles = StyleSheet.create({
  container: {
    padding: 0,
    margin: 0,
    position: 'relative',
    overflow: 'visible'
  }
})

const selectContainerStyles = (tokens) => ({
  minHeight: tokens.minHeight,
  minWidth: tokens.minWidth,
  backgroundColor: tokens.containerBackgroundColor
})

const getInitialOpen = (items, selectedId) =>
  items
    .filter(
      (item) =>
        item.items &&
        item.items.some((nestedItem) => (nestedItem.id ?? nestedItem.label) === selectedId)
    )
    .map((item) => item.id ?? item.label)

export const Listbox = React.forwardRef(
  (
    {
      items = [],
      firstItemRef = null, // focus will be moved to this one once within the menu
      parentRef = null, // to return focus to after leaving the last menu item
      selectedId: defaultSelectedId,
      LinkRouter,
      itemRouterProps,
      onClose,
      copy = 'en',
      dictionary = defaultDictionary,
      variant,
      tokens,
      testID
    },
    ref
  ) => {
    const initialOpen = getInitialOpen(items, defaultSelectedId)
    const [selectedId, setSelectedId] = React.useState(defaultSelectedId)
    const [activeSecondLevelGroup, setActiveSecondLevelGroup] = React.useState(null)
    const listboxTokens = useThemeTokens('Listbox', tokens, variant)

    const containerRef = React.useRef(null)

    // We need to keep track of each item's ref in order to be able to
    // focus on a specific item via keyboard navigation
    const itemRefs = React.useRef([])
    if (firstItemRef?.current) itemRefs.current[0] = firstItemRef.current
    const [focusedIndex, setFocusedIndex] = React.useState(0)
    const handleKeydown = React.useCallback(
      (event) => {
        const nextItemRef = itemRefs.current[focusedIndex + 1]
        const prevItemRef = itemRefs.current[focusedIndex - 1]
        if (event.key === 'ArrowUp' || (event.shiftKey && event.key === 'Tab')) {
          // Move the focus to the previous item or to the parent one if on the first
          if (prevItemRef) {
            event.preventDefault()
            prevItemRef.focus()
          } else if (parentRef) parentRef.current?.focus()
          setFocusedIndex(focusedIndex - 1)
        } else if ((event.key === 'ArrowDown' || event.key === 'Tab') && nextItemRef) {
          event.preventDefault()
          setFocusedIndex(focusedIndex + 1)
          nextItemRef.focus()
        } else if (event.key === 'Escape') {
          // Close the dropdown
          parentRef?.current?.click()
          // Return focus to the dropdown control after leaving the last item
          parentRef?.current?.focus()
          if (onClose) onClose(event)
        } else if (!nextItemRef && firstItemRef) {
          // If the last item is focused, move the focus to the first one
          event.preventDefault()
          setFocusedIndex(0)
          firstItemRef.current?.focus()
        }
      },
      [focusedIndex, onClose, parentRef, firstItemRef]
    )

    // Add listeners for mouse clicks outside and for key presses
    React.useEffect(() => {
      if (Platform.OS === 'web') {
        const handleOutsideClick = (e) => {
          const isInsideListbox = containerRef.current?.contains(e.target)
          const isInsideTrigger = parentRef?.current?.contains(e.target)
          if (!isInsideListbox && !isInsideTrigger) onClose(e)
        }
        window.addEventListener('click', handleOutsideClick)
        window.addEventListener('keydown', handleKeydown)
        window.addEventListener('touchstart', handleOutsideClick)
        return () => {
          window.removeEventListener('click', handleOutsideClick)
          window.removeEventListener('keydown', handleKeydown)
          window.removeEventListener('touchstart', handleOutsideClick)
        }
      }
      return () => {}
    }, [onClose, handleKeydown, parentRef])

    const contextValue = {
      selectedId,
      setSelectedId,
      activeSecondLevelGroup,
      setActiveSecondLevelGroup
    }

    return (
      <ListboxContext.Provider value={contextValue}>
        <ExpandCollapse initialOpen={initialOpen} maxOpen={1} ref={ref}>
          {(expandProps) => (
            <View
              ref={containerRef}
              style={[styles.container, selectContainerStyles(listboxTokens)]}
              accessibilityRole="combobox"
              testID={testID}
            >
              {items.map((item, index) => {
                const { id, label, items: nestedItems } = item
                const itemId = id ?? label

                // Give the list of refs.
                const itemRef = (currentItemRef) => {
                  itemRefs.current[index] = currentItemRef
                  return currentItemRef
                }

                if (!nestedItems && activeSecondLevelGroup) {
                  return null
                }

                return nestedItems ? (
                  <ListboxGroup
                    {...item}
                    expandProps={expandProps}
                    LinkRouter={LinkRouter}
                    itemRouterProps={itemRouterProps}
                    prevItemRef={itemRefs.current[index - 1] ?? null}
                    nextItemRef={itemRefs.current[index + 1] ?? null}
                    ref={index === 0 ? firstItemRef : itemRef}
                    key={itemId}
                    copy={copy}
                    dictionary={dictionary}
                    variant={variant}
                    tokens={tokens}
                    onClose={onClose}
                  />
                ) : (
                  <ListboxItem
                    {...item}
                    key={itemId}
                    id={itemId}
                    LinkRouter={LinkRouter}
                    itemRouterProps={itemRouterProps}
                    prevItemRef={itemRefs.current[index - 1] ?? null}
                    nextItemRef={itemRefs.current[index + 1] ?? null}
                    ref={index === 0 ? firstItemRef : itemRef}
                    variant={variant}
                    tokens={tokens}
                  />
                )
              })}
            </View>
          )}
        </ExpandCollapse>
      </ListboxContext.Provider>
    )
  }
)

Listbox.displayName = 'Listbox'

Listbox.propTypes = {
  ...withLinkRouter.propTypes,
  tokens: getTokensPropType('Listbox'),
  /**
   * Focus will be moved to the item with this ref once within the menu.
   */
  firstItemRef: PropTypes.object,
  /**
   * Focus will be returned to the dropdown control with this ref after leaving
   * the last menu item.
   */
  parentRef: PropTypes.object,
  /**
   * `Listbox` items
   */
  items: PropTypes.array,
  /**
   * To select an item by default
   */
  selectedId: PropTypes.string,
  /**
   * onClose event
   */
  onClose: PropTypes.func,
  /**
   * Test ID for testing
   */
  testID: PropTypes.string,
  /**
   * Select English or French copy
   */
  copy: PropTypes.oneOf(['en', 'fr']),
  /**
   * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
   */
  dictionary: PropTypes.shape({
    en: PropTypes.shape({
      closeMenu: PropTypes.string.isRequired
    }),
    fr: PropTypes.shape({
      closeMenu: PropTypes.string.isRequired
    })
  }),
  /**
   * Listbox variant
   */
  variant: variantProp.propType
}
