import React from 'react'
import PropTypes from 'prop-types'
import { View, StyleSheet, Dimensions, SafeAreaView, Platform, ScrollView } from 'react-native'
import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider/useThemeTokens'
import { applyTextStyles } from '../ThemeProvider/utils/styles'
import { containUniqueFields } from '../utils/containUniqueFields'
import { getTokensPropType, selectTokens } from '../utils/props/tokens'
import { getPressHandlersWithArgs } from '../utils/pressability'
import { useOverlaidPosition } from '../utils/useOverlaidPosition'
import { useCopy } from '../utils/useCopy'
import { useMultipleInputValues } from '../utils/input'
import { useResponsiveProp } from '../utils/useResponsiveProp'
import { variantProp } from '../utils/props/variantProp'
import { useScrollBlocking } from '../utils/useScrollBlocking'
import { dictionary as defaultDictionary } from './dictionary'
import { useViewport } from '../ViewportProvider/useViewport'
import { Box } from '../Box/Box'
import { Button } from '../Button/Button'
import { ButtonDropdown } from '../Button/ButtonDropdown'
import { CheckboxGroup } from '../Checkbox/CheckboxGroup'
import { Divider } from '../Divider/Divider'
import { FlexGridRow } from '../FlexGrid/FlexGridRow/FlexGridRow'
import { FlexGridCol } from '../FlexGrid/FlexGridCol/FlexGridCol'
import { Spacer } from '../Spacer/Spacer'
import { StackView } from '../StackView/StackView'
import { Typography } from '../Typography/Typography'
import { TextButton } from '../Link/TextButton'
import { ModalOverlay } from './ModalOverlay'

const selectHeaderTokens = ({
  contentMarginLeft,
  contentMarginRight,
  contentMarginTop,
  contentPaddingLeft,
  contentPaddingRight
}) => ({
  marginLeft: contentMarginLeft,
  marginRight: contentMarginRight,
  marginTop: contentMarginTop,
  paddingLeft: contentPaddingLeft,
  paddingRight: contentPaddingRight,
  flexGrow: 1
})

const selectControlsTokens = ({
  contentMarginBottom,
  contentMarginLeft,
  contentMarginRight,
  contentPaddingLeft,
  contentPaddingRight
}) => ({
  marginBottom: contentMarginBottom,
  marginLeft: contentMarginLeft,
  marginRight: contentMarginRight,
  paddingLeft: contentPaddingLeft,
  paddingRight: contentPaddingRight
})

const selectContainerStyle = (windowHeight, windowWidth) => ({
  height: windowHeight,
  width: windowWidth
})

const TOTAL_COLUMNS = 12

export const MultiSelectFilter = React.forwardRef(
  (
    {
      label,
      subtitle,
      id = label,
      variant,
      tokens,
      items = [],
      values,
      leadIcon,
      maxHeight = false,
      initialValues,
      maxValues,
      onChange,
      onConfirm = () => {},
      onCancel = () => {},
      onOpen = () => {},
      copy = 'en',
      readOnly = false,
      inactive = false,
      rowLimit = 12,
      dictionary = defaultDictionary,
      ...rest
    },
    ref
  ) => {
    const viewport = useViewport()
    const { currentValues, setValues } = useMultipleInputValues({
      initialValues,
      values,
      maxValues,
      onChange,
      readOnly
    })
    const [isOpen, setIsOpen] = React.useState(false)
    const [checkedIds, setCheckedIds] = React.useState(currentValues ?? [])
    const [maxWidth, setMaxWidth] = React.useState(false)
    const isSelected = currentValues.length > 0

    const {
      headerFontColor,
      headerFontSize,
      buttonDirection,
      buttonIconBackgroundColor,
      headerFontWeight,
      headerLineHeight,
      subHeaderFontWeight,
      subHeaderFontSize,
      maxHeightSize,
      maxWidthSize,
      subHeaderLineHeight,
      minHeight,
      minWidth,
      labelFontName,
      labelFontSize,
      labelFontWeight,
      labelColor,
      labelLineHeight,
      labelPaddingTop,
      labelPaddingBottom,
      labelPaddingLeft,
      labelPaddingRight,
      buttonBorderColor,
      buttonIconSize,
      buttonIconPadding,
      subtitleColor,
      dividerColor,
      iconColor,
      buttonBackgroundColor,
      iconColorSelected,
      buttonBackgroundColorSelected,
      containerBorderColor,
      ...restTokens
    } = useThemeTokens(
      'MultiSelectFilter',
      tokens,
      { ...variant, maxHeight, maxWidth, selected: isSelected },
      { viewport }
    )
    const dropdownTokens = {
      borderColor: buttonBorderColor,
      iconSize: buttonIconSize,
      iconPadding: buttonIconPadding,
      lineHeight: labelLineHeight,
      fontName: labelFontName,
      fontSize: labelFontSize,
      fontWeight: labelFontWeight,
      color: labelColor,
      paddingTop: labelPaddingTop,
      paddingBottom: labelPaddingBottom,
      paddingLeft: labelPaddingLeft,
      paddingRight: labelPaddingRight,
      iconBackground: buttonIconBackgroundColor,
      iconColor: isSelected ? iconColorSelected : iconColor,
      backgroundColor: isSelected ? buttonBackgroundColorSelected : buttonBackgroundColor,
      ...(leadIcon && { leadIcon })
    }
    const getButtonDropdownTokens = useThemeTokensCallback(
      'ButtonDropdown',
      dropdownTokens,
      variant
    )
    const getButtonTokens = (buttonState) =>
      selectTokens('ButtonDropdown', getButtonDropdownTokens(buttonState))
    const getCopy = useCopy({ dictionary, copy })
    const colSizeNotMobile = items.length > rowLimit ? 2 : 1
    const colSize = viewport !== 'xs' ? colSizeNotMobile : 1

    let rowLength = items.length
    if (viewport !== 'xs' && colSize === 2) {
      rowLength = Math.ceil(items.length / 2)
    }

    React.useEffect(() => {
      setMaxWidth(items.length >= rowLimit)
    }, [items.length, rowLimit])

    React.useEffect(() => setCheckedIds(currentValues ?? []), [currentValues])

    React.useEffect(() => {
      if (isOpen && onOpen) {
        onOpen()
      }
    }, [isOpen, onOpen])

    const uniqueFields = ['id', 'label']
    if (!containUniqueFields(items, uniqueFields)) {
      throw new Error(`MultiSelectFilter items must have unique ${uniqueFields.join(', ')}`)
    }
    // Pass an object of relevant component state as first argument for any passed-in press handlers
    const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])

    const handleChange = (event) => {
      if (pressHandlers.onPress) pressHandlers?.onPress(event)
      setIsOpen(!isOpen)
    }

    const onApply = (e) => {
      setValues(e)
      setIsOpen(false)
      onConfirm(e)
    }

    const onClear = () => {
      setCheckedIds(() => [])
      setValues([])
      setIsOpen(false)
      onCancel()
    }

    const onClose = () => {
      setIsOpen(false)
      onCancel()
    }

    const { align, offsets } = useResponsiveProp({
      xs: { align: { top: 'top', left: 'left' } },
      sm: {
        align: { top: 'bottom', left: 'left' },
        offsets: { vertical: 4 }
      }
    })

    const headerStyles = applyTextStyles({
      fontSize: headerFontSize,
      fontWeight: headerFontWeight,
      fontColor: headerFontColor
    })

    const subeHeaderStyles = applyTextStyles({
      fontSize: subHeaderFontSize,
      fontWeight: subHeaderFontWeight,
      fontColor: subtitleColor
    })

    const { overlaidPosition, onTargetLayout, isReady, sourceRef } = useOverlaidPosition({
      isShown: isOpen,
      offsets,
      align
    })

    const [isScrolling, setIsScrolling] = React.useState(false)
    const [scrollViewHeight, setScrollViewHeight] = React.useState(0)
    const [rowHeight, setRowHeight] = React.useState(0)
    const modalRef = useScrollBlocking(isOpen && viewport === 'xs')
    const windowWidth = Dimensions.get('window').width
    const windowHeight = Dimensions.get('window').height

    React.useEffect(() => {
      if (rowHeight > scrollViewHeight) {
        setIsScrolling(true)
      } else {
        setIsScrolling(false)
      }
    }, [scrollViewHeight, rowHeight])

    const handleScrollViewLayout = (event) => {
      const { height } = event.nativeEvent.layout
      setScrollViewHeight(height)
    }

    const handleRowLayout = (event) => {
      const { height } = event.nativeEvent.layout
      setRowHeight(height)
    }

    const headerContent = (
      <View style={[selectHeaderTokens(restTokens), styles.container]}>
        <FlexGridRow>
          <View>
            <Typography tokens={{ ...headerStyles, lineHeight: headerLineHeight }}>
              {getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label)}
            </Typography>
          </View>
        </FlexGridRow>
        {subtitle && (
          <>
            <Spacer space={4} />
            <FlexGridRow>
              <Typography tokens={{ ...subeHeaderStyles, lineHeight: subHeaderLineHeight }}>
                {subtitle}
              </Typography>
            </FlexGridRow>
          </>
        )}
        <Spacer space={4} />
        <View style={styles.options}>
          <ScrollView onLayout={handleScrollViewLayout} style={styles.scrollContainer}>
            <FlexGridRow distribute="between" onLayout={handleRowLayout}>
              {[...Array(colSize).keys()].map((i) => (
                <FlexGridCol xs={TOTAL_COLUMNS / colSize} key={i}>
                  <CheckboxGroup
                    items={items.slice(i * rowLength, (i + 1) * rowLength)}
                    checkedIds={checkedIds}
                    onChange={(e) => setCheckedIds(e, i)}
                  />
                  <Spacer size={4} />
                </FlexGridCol>
              ))}
            </FlexGridRow>
          </ScrollView>
        </View>
      </View>
    )

    const controlsContent = (
      <>
        {isScrolling && (
          <Divider
            tokens={{
              color: dividerColor
            }}
          />
        )}
        <Spacer space={4} />
        <View style={selectControlsTokens(restTokens)}>
          <FlexGridRow horizontalAlign={buttonDirection === 'column' ? 'center' : 'start'}>
            <StackView
              direction={buttonDirection}
              space={3}
              tokens={{
                alignItems: 'center',
                ...(viewport === 'xs' && { flexGrow: 1 })
              }}
            >
              <Button
                onPress={() => onApply(checkedIds)}
                variant={{
                  priority: 'high',
                  ...(viewport === 'xs' && { width: 'full' })
                }}
              >
                {getCopy('applyButtonLabel')}
              </Button>
              <Box>
                <TextButton onPress={onClear}>{getCopy('clearButtonLabel')}</TextButton>
              </Box>
            </StackView>
          </FlexGridRow>
        </View>
      </>
    )

    return (
      <>
        <ButtonDropdown
          ref={sourceRef}
          key={id}
          {...pressHandlers}
          value={isOpen}
          selected={isSelected}
          label={label}
          onChange={handleChange}
          tokens={getButtonTokens}
          inactive={inactive}
        />
        {isOpen && viewport === 'xs' && (
          <ModalOverlay
            overlaidPosition={{ top: 0, left: 0 }}
            onClose={onClose}
            maxHeight={true}
            maxHeightSize={windowHeight}
            maxWidthSize={windowWidth}
            minHeight={windowHeight}
            minWidth={windowWidth}
            tokens={{
              ...tokens,
              maxWidth: true,
              borderColor: containerBorderColor
            }}
            copy={copy}
            isReady={isReady}
            onLayout={onTargetLayout}
            ref={modalRef}
            enableFullscreen
          >
            <SafeAreaView
              style={[selectContainerStyle(windowHeight, windowWidth), styles.content]}
              ref={ref}
            >
              {headerContent}
              <View style={styles.controls}>{controlsContent}</View>
            </SafeAreaView>
          </ModalOverlay>
        )}
        {isOpen && viewport !== 'xs' && (
          <ModalOverlay
            onClose={onClose}
            overlaidPosition={overlaidPosition}
            maxHeight={items.length >= rowLimit ? true : maxHeight}
            maxHeightSize={maxHeightSize}
            maxWidthSize={maxWidthSize}
            minHeight={minHeight}
            minWidth={minWidth}
            tokens={{
              ...tokens,
              maxWidth: items.length >= rowLimit ? true : maxWidth,
              borderColor: containerBorderColor
            }}
            copy={copy}
            isReady={isReady}
            onLayout={onTargetLayout}
            ref={ref}
          >
            {headerContent}
            {controlsContent}
          </ModalOverlay>
        )}
      </>
    )
  }
)

MultiSelectFilter.displayName = 'MultiSelectFilter'

const styles = StyleSheet.create({
  container: { flex: 1 },
  controls: Platform.select({
    web: {},
    default: {
      flex: 0.35
    }
  }),
  content: {
    flex: 1,
    justifyContent: 'space-between',
    backgroundColor: 'transparent'
  },
  options: {
    flex: 1
  },
  scrollContainer: {
    padding: 1
  }
})

// If a language dictionary entry is provided, it must contain every key
const dictionaryContentShape = PropTypes.shape({
  filterByLabel: PropTypes.string.isRequired,
  applyButtonLabel: PropTypes.string.isRequired,
  clearButtonLabel: PropTypes.string.isRequired
})

MultiSelectFilter.propTypes = {
  /**
   * The text displayed to the user in a ButtonDropdown.
   */
  label: PropTypes.string.isRequired,
  /**
   * The text for the subtitle. Can also be JSX.
   */
  subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  /**
   * An optional unique string may be provided to identify the ButtonDropdown.
   * If not provided, the label is used.
   */
  id: PropTypes.string,
  /**
   * Sets the variant for ButtonDropdown element.
   */
  variant: variantProp.propType,
  /**
   * Sets the tokens for MultiSelectFilter element.
   */
  tokens: getTokensPropType('MultiSelectFilter'),
  /**
   * A valid UDS icon component (imported from a UDS palette) rendered at the
   * start of the ButtonDropdown trigger.
   */
  leadIcon: PropTypes.elementType,
  /**
   * The options a user may select.
   */
  items: PropTypes.arrayOf(
    PropTypes.shape({
      /**
       * The text displayed to the user with a checkbox, describing this option.
       */
      label: PropTypes.string.isRequired,
      /**
       * An optional unique string may be provided to identify this option.
       * If not provided, the label is used.
       */
      id: PropTypes.string,
      /**
       * An optional Checkbox description.
       */
      description: PropTypes.string
    })
  ),
  /**
   * If the selected item(s) in the checkbox group(s) are to be controlled externally by
   * a parent component, pass an array of strings as well as an `onChange` handler.
   * Passing an array for "values" makes the MultiSelectFilter a "controlled" component that
   * expects its state to be handled via `onChange` and so doesn't handle it itself.
   */
  values: PropTypes.arrayOf(PropTypes.string),
  /**
   * If `values` is not passed, making the MultiSelectFilter an "uncontrolled" component
   * managing its own selected state, a default set of selections may be provided.
   * Changing the `initialValues` does not change the user's selections.
   */
  initialValues: PropTypes.arrayOf(PropTypes.string),
  /**
   * If provided, sets a maximum number of items a user may select at once.
   */
  maxValues: PropTypes.number,
  /**
   * If provided sets maxHeight to be active
   */
  maxHeight: PropTypes.bool,
  /**
   * If provided, this function is called when the current selection is changed
   * and is passed an array of the `id`s of all currently selected `items`.
   */
  onChange: PropTypes.func,
  /**
   * If provided, this function is called when the selection is confirmed.
   */
  onConfirm: PropTypes.func,
  /**
   * If provided, this function is called when the modal with options is opened.
   * */
  onOpen: PropTypes.func,
  /**
   * If provided, this function is called when `MultiSelectFilter` is closed.
   * */
  onCancel: PropTypes.func,
  /**
   * Select English or French copy for the accessible label.
   */
  copy: PropTypes.oneOf(['en', 'fr']),
  /**
   * Custom dictionary containing the labels
   */
  dictionary: PropTypes.shape({
    en: dictionaryContentShape,
    fr: dictionaryContentShape
  }),
  /**
   * If true, the ButtonDropdown cannot be selected by the user and simply show their current state.
   */
  readOnly: PropTypes.string,
  /**
   * If true, the MultiSelectFilter cannot be interacted with, ButtonDropdown is
   * set as `disabled` and if the theme supports `inactive` appearances rules, these
   * are applied.
   */
  inactive: PropTypes.string,
  /**
   * Sets the maximum number of items in one column. If number of items are more
   * than the `rowLimit`, they will be rendered in 2 columns.
   */
  rowLimit: PropTypes.number
}
