import React from 'react'
import PropTypes from 'prop-types'
import { Platform, StyleSheet, Text, View } from 'react-native'
import { buttonPropTypes, textAndA11yText } from './propTypes'
import { ButtonBase } from './ButtonBase'
import { applyTextStyles } from '../ThemeProvider/utils/styles'
import { useThemeTokensCallback } from '../ThemeProvider/useThemeTokens'
import { a11yProps } from '../utils/props/a11yProps'
import { getTokensPropType, selectTokens } from '../utils/props/tokens'
import { focusHandlerProps } from '../utils/props/handlerProps'
import { resolvePressableState, getPressHandlersWithArgs } from '../utils/pressability'
import { useInputValue } from '../utils/input'
import { Icon } from '../Icon/Icon'

const FULL_WIDTH_STYLE = 'full'

const selectIconTokens = ({
  icon,
  iconPosition,
  iconSpace,
  iconSize,
  iconColor,
  iconBackground,
  iconBorderRadius,
  iconAlignSelf,
  iconPadding,
  iconTranslateX,
  iconTranslateY
}) => ({
  icon,
  iconPosition,
  iconSpace,
  iconWrapperStyle: {
    backgroundColor: iconBackground,
    borderRadius: iconBorderRadius,
    alignSelf: iconAlignSelf,
    padding: iconPadding,
    ...Platform.select({
      // TODO: https://github.com/telus/universal-design-system/issues/487
      web: { transition: 'color 200ms, background 200ms' }
    })
  },
  iconTokens: {
    size: iconSize,
    color: iconColor,
    translateX: iconTranslateX,
    translateY: iconTranslateY
  }
})

const selectDescriptionTextStyles = (tokens) => ({
  ...applyTextStyles({
    fontName: tokens?.descriptionFontName,
    fontSize: tokens?.descriptionFontSize,
    fontWeight: tokens?.descriptionFontWeight,
    lineHeight: tokens?.descriptionLineHeight,
    fontColor: tokens?.color
  }),
  paddingBottom: tokens?.descriptionTextPaddingBottom
})

const selectLeadIconTokens = (tokens) => ({
  color: tokens?.leadIconColor,
  backgroundColor: tokens?.leadIconBackgroundColor,
  size: tokens?.leadIconSize,
  borderRadius: tokens?.leadIconBorderRadius,
  padding: tokens?.leadIconPadding
})

const selectLeadIconContainerStyles = (tokens) => ({
  paddingTop: tokens?.leadIconContainerPaddingTop,
  paddingBottom: tokens?.leadIconContainerPaddingBottom,
  paddingLeft: tokens?.leadIconContainerPaddingLeft,
  paddingRight: tokens?.leadIconContainerPaddingRight
})

const selectTextContainerStyles = (tokens) => ({
  paddingLeft: tokens?.textPaddingLeft,
  paddingRight: tokens?.textPaddingRight
})

const selectStackedContentStyles = (tokens, iconPosition, isFullWidth) => ({
  ...staticStyles.stackedContent,
  gap: tokens?.iconSpace,
  flexDirection: iconPosition === 'left' ? 'row' : 'row-reverse',
  ...(Platform.OS === 'web' && { flex: 1 }),
  ...(isFullWidth && { justifyContent: 'space-between', flex: 1 })
})

export const ButtonDropdown = React.forwardRef(
  (
    {
      value,
      initialValue,
      onChange,
      label,
      tokens,
      variant,
      inactive = false,
      readOnly = false,
      children = null,
      accessibilityRole = 'radio',
      description,
      singleOption,
      heightFull = true,
      ...props
    },
    ref
  ) => {
    const isFullWidth = variant?.width === FULL_WIDTH_STYLE

    const { currentValue: isOpen, setValue: setIsOpen } = useInputValue(
      {
        value,
        initialValue,
        onChange,
        readOnly
      },
      'useButtonDropdownValues'
    )

    const extraState = {
      open: isOpen,
      inactive,
      ...variant
    }

    const getTokens = useThemeTokensCallback('ButtonDropdown', tokens, extraState)

    const getButtonTokens = (buttonState) => ({
      ...selectTokens('Button', getTokens(buttonState)),
      iconSpace: props?.icon ? getTokens(buttonState)?.iconSpace : 0,
      ...(isFullWidth && { width: 'full' })
    })

    // Pass an object of relevant component state as first argument for any passed-in press handlers
    const pressHandlers = getPressHandlersWithArgs(props, [{ label, open: isOpen }])

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

    return (
      <ButtonBase
        ref={ref}
        {...pressHandlers}
        onPress={handlePress}
        tokens={getButtonTokens}
        heightFull={heightFull}
        inactive={singleOption || inactive}
        icon={() => null}
        accessibilityRole={accessibilityRole}
        {...props}
      >
        {({ textStyles, ...buttonState }) => {
          // TODO: once Icon/IconButton designs are stable, see if this sort of styling around
          // an icon should go in Icon itself, or possibly via an IconText token set. Related issues:
          // - Icon: https://github.com/telus/universal-design-system/issues/327
          // - IconButton: https://github.com/telus/universal-design-system/issues/281
          // - Token sets: https://github.com/telus/universal-design-system/issues/782

          const itemTokens = getTokens(buttonState)
          const leadIcon = itemTokens?.leadIcon

          const {
            iconTokens,
            iconPosition,
            iconWrapperStyle,
            icon: IconComponent
          } = selectIconTokens(itemTokens)

          const iconContent =
            IconComponent && !singleOption ? (
              <View style={iconWrapperStyle}>
                <Icon icon={IconComponent} tokens={iconTokens} />
              </View>
            ) : null

          const childrenContent = () =>
            typeof children === 'function'
              ? children({ ...resolvePressableState(buttonState, extraState), textStyles })
              : children

          const content = children ? (
            childrenContent()
          ) : (
            <View style={staticStyles.contentContainer}>
              {leadIcon && (
                <View style={selectLeadIconContainerStyles(itemTokens)}>
                  <Icon icon={leadIcon} tokens={selectLeadIconTokens(itemTokens)} />
                </View>
              )}
              <View style={[staticStyles.textContainer, selectTextContainerStyles(itemTokens)]}>
                <Text style={textStyles}>{label}</Text>
                {description && (
                  <Text style={selectDescriptionTextStyles(itemTokens)}>{description}</Text>
                )}
              </View>
            </View>
          )

          return (
            <View style={selectStackedContentStyles(itemTokens, iconPosition, isFullWidth)}>
              {iconContent}
              {content}
            </View>
          )
        }}
      </ButtonBase>
    )
  }
)
ButtonDropdown.displayName = 'ButtonDropdown'

ButtonDropdown.propTypes = {
  ...a11yProps.types,
  ...focusHandlerProps.types,
  ...buttonPropTypes,
  children: textAndA11yText,
  tokens: getTokensPropType('ButtonDropdown'),
  /**
   * Keeps the dropdown's value visible but prevents the user from changing it.
   */
  readOnly: PropTypes.bool,

  /**
   * Callback called when a controlled ButtonDropdown gets interacted with.
   */
  onChange: PropTypes.func,
  /**
   * `value` prop is being used to set the 'open' state of ButtonDropdown. Use it for
   *  controlled ButtonDropdown. For uncontrolled ButtonDropdown, use `initialValue`.
   */
  value: PropTypes.bool,
  /**
   * Use `initialValue` to provide the initial value for an uncontrolled version.
   */
  initialValue: PropTypes.bool,
  /**
   * The label of ButtonDropdown.
   */
  label: PropTypes.string,
  /**
   * By default, `ButtonDropdown` is treated by accessibility tools as a radio button.
   */
  accessibilityRole: PropTypes.string,
  /**
   * The description of ButtonDropdown.
   */
  description: PropTypes.string,
  /**
   * Use this prop to render the ButtonDropdown as display only without any interaction when there is only one option.
   */
  singleOption: PropTypes.bool
}

const staticStyles = StyleSheet.create({
  textContainer: {
    alignItems: 'flex-start',
    flexShrink: 1,
    ...(Platform.OS === 'web' && { flex: 1 })
  },
  contentContainer: {
    flexDirection: 'row',
    alignContent: 'center',
    alignItems: 'center',
    ...(Platform.OS === 'web' && { flex: 1 }),
    flexShrink: 1
  },
  stackedContent: {
    flexDirection: 'row',
    alignItems: 'center'
  }
})
