import React from 'react'
import PropTypes from 'prop-types'
import { Platform, StyleSheet, View } from 'react-native'

import { InputLabel } from '../InputLabel/InputLabel'
import { ButtonBase } from '../Button/ButtonBase'
import { StackView } from '../StackView/StackView'
import { useThemeTokensCallback } from '../ThemeProvider/useThemeTokens'
import { applyShadowToken } from '../ThemeProvider/utils/styles'
import { a11yProps } from '../utils/props/a11yProps'
import { focusHandlerProps } from '../utils/props/handlerProps'
import { getTokensPropType, selectTokens } from '../utils/props/tokens'
import { pressProps } from '../utils/props/pressProps'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { useUniqueId } from '../utils/useUniqueId'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { useInputValue } from '../utils/input'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([
  a11yProps,
  focusHandlerProps,
  pressProps,
  viewProps
])

// We need to drop the icon before passing it to the `ButtonBase`, because it's
// being handled separately in this case
const selectButtonTokens = ({ icon: _, ...tokens }, { trackHeight, outerBorderGapButton }) =>
  selectTokens('Button', {
    ...tokens,
    // Width tokens are applied to our inner track. Disable Button width token so it wraps our track width.
    outerBorderGap: outerBorderGapButton,
    height: trackHeight,
    width: null
  })

// Map and rename icon-specific tokens to name used within Icon
const selectIconTokens = ({ iconSize, iconColor }) => ({
  size: iconSize,
  color: iconColor
})

const selectTrackSwitchStyles = ({ switchSize, width, trackHeight }) => ({
  width,
  height: Math.max(switchSize, trackHeight)
})

const selectTrackStyles = ({ trackBorderWidth, trackBorderColor, trackBorderRadius, width }) => ({
  borderWidth: trackBorderWidth,
  borderColor: trackBorderColor,
  borderRadius: trackBorderRadius,
  width
})

const selectSwitchStyles = ({
  switchSize,
  switchColor,
  switchBorderWidth,
  switchBorderColor,
  switchBorderRadius,
  switchShadow
}) => ({
  position: 'absolute',
  width: switchSize,
  height: switchSize,
  backgroundColor: switchColor,
  borderWidth: switchBorderWidth,
  borderColor: switchBorderColor,
  borderRadius: switchBorderRadius,
  ...applyShadowToken(switchShadow),
  ...Platform.select({
    // TODO: https://github.com/telus/universal-design-system/issues/487
    web: { transition: 'transform 200ms' }
  })
})

const selectMobileTokens = (tokens) => ({
  ...Platform.select({
    web: {},
    default: {
      switchSize: tokens?.mobileSwitchSize,
      trackHeight: tokens?.mobileTrackHeight,
      width: tokens?.mobileWidth,
      trackBorderWidth: tokens?.mobileTrackBorderWidth
    }
  })
})

const selectLabelStyles = ({ labelMarginLeft }) => ({ marginLeft: labelMarginLeft })
const selectLabelTokens = ({
  labelColor,
  labelFontName,
  labelFontSize,
  labelFontWeight,
  labelLineHeight
}) => ({
  color: labelColor,
  fontName: labelFontName,
  fontWeight: labelFontWeight,
  fontSize: labelFontSize,
  lineHeight: labelLineHeight
})

export const ToggleSwitch = React.forwardRef(
  (
    {
      copy = 'en',
      value,
      initialValue,
      onChange,
      id,
      label,
      inactive,
      tokens,
      tooltip,
      variant,
      accessibilityRole = 'switch',
      accessibilityLabel = label,
      togglePosition = 'end',
      ...rest
    },
    ref
  ) => {
    const getTokens = useThemeTokensCallback('ToggleSwitch', tokens, variant)
    const themeTokens = getTokens()

    const { currentValue, setValue } = useInputValue({
      value,
      initialValue,
      onChange
    })

    const handlePress = (event) => setValue(!currentValue, event)
    const getButtonTokens = (buttonState) =>
      selectButtonTokens(
        getTokens(buttonState, selectMobileTokens(themeTokens)),
        getTokens(themeTokens, selectMobileTokens(themeTokens))
      )
    const uniqueId = useUniqueId('toggleSwitch')
    const inputId = id ?? uniqueId

    return (
      <StackView
        space={themeTokens.space}
        direction={togglePosition === 'start' ? 'row-reverse' : 'row'}
        tokens={{
          alignItems: 'center',
          justifyContent: themeTokens.containerWidth ? 'space-between' : 'start',
          width: themeTokens.containerWidth
        }}
      >
        {Boolean(label) && (
          <View style={[selectLabelStyles(themeTokens), staticStyles.containText]}>
            <InputLabel
              copy={copy}
              forId={inputId}
              label={label}
              tokens={selectLabelTokens(themeTokens)}
              tooltip={tooltip}
            />
          </View>
        )}
        <ButtonBase
          id={id}
          ref={ref}
          selected={currentValue}
          inactive={inactive}
          tokens={getButtonTokens}
          accessibilityLabel={accessibilityLabel}
          accessibilityRole={accessibilityRole}
          accessibilityState={{ checked: currentValue }}
          onPress={handlePress}
          {...selectProps(rest)}
        >
          {(buttonState) => {
            const stateTokens = getTokens(buttonState, selectMobileTokens(themeTokens))
            const IconComponent = stateTokens.icon
            const switchStyles = selectSwitchStyles(stateTokens)
            const trackStyles = selectTrackStyles(stateTokens)
            const iconTokens = selectIconTokens(stateTokens)
            const trackSwitchStyles = selectTrackSwitchStyles(stateTokens)
            const { switchSize, trackHeight, width, trackBorderWidth, switchOffset } = stateTokens
            // If drag-slide support is needed, use a PanResponder and apply these to an Animated value.
            // Use translate transforms for smoothest non-thread-blocking animations and to allow drag.
            // Two different translates depending on the switchSize and trackSize relationship.
            const isSwitchTallerThanTrack = switchSize >= trackHeight
            const slideStart = isSwitchTallerThanTrack ? 0 : trackBorderWidth
            const slideEnd = isSwitchTallerThanTrack
              ? width - switchSize
              : width - switchSize - trackBorderWidth
            let translateX = buttonState.selected ? slideEnd : slideStart
            if (switchOffset != null) translateX = switchOffset
            const switchPositionStyle = { transform: [{ translateX }] }

            return (
              <View nativeID="trackSwitch" style={[trackSwitchStyles, staticStyles.trackSwitch]}>
                <View style={[staticStyles.track, trackStyles]} />
                <View style={[staticStyles.switch, switchStyles, switchPositionStyle]}>
                  {IconComponent && <IconComponent {...iconTokens} />}
                </View>
              </View>
            )
          }}
        </ButtonBase>
      </StackView>
    )
  }
)
ToggleSwitch.displayName = 'ToggleSwitch'

ToggleSwitch.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Whether the English or French copy will be used (e.g. for accessibility labels).
   */
  copy: PropTypes.oneOf(['en', 'fr']),
  tokens: getTokensPropType('ToggleSwitch'),
  variant: variantProp.propType,
  /**
   * If this is a "controlled" component and a parent handles its selected state,
   * tells the switch if it is on (true) or off (false).
   */
  value: PropTypes.bool,
  /**
   * If this is an "uncontrolled" component that handles its own selected state,
   * optionally tells the switch if it should default to on (true) or off (false).
   */
  initialValue: PropTypes.bool,
  /**
   * Function called when the switch is toggled, with one boolean argument that is the
   * new position of the switch, on (true) or off (false). For "controlled" components,
   * this should always be passed and used to control the state of the switch.
   */
  onChange: PropTypes.func,
  /**
   * Input ID.
   */
  id: PropTypes.string,
  /**
   * An optional label.
   */
  label: PropTypes.string,
  /**
   * If passed, the switch does not respond to user input and may recieve different
   * theme tokens if the theme supports inactive appearance.
   */
  inactive: PropTypes.bool,
  /**
   * Content of an optional Tooltip. If set, a tooltip button will be shown next to the label.
   */
  tooltip: PropTypes.string,
  /**
   * Controls the position of the switch in relation to the label.
   * - 'start': switch on the start, before the `label` and `tooltip`.
   * - 'end': switch to the end, after the `label` and `tooltip` (default).
   */
  togglePosition: PropTypes.oneOf(['start', 'end'])
}

const staticStyles = StyleSheet.create({
  track: {
    flexDirection: 'row'
  },
  switch: {
    alignItems: 'center',
    justifyContent: 'center'
  },
  containText: {
    flexShrink: 1
  },
  trackSwitch: {
    flexDirection: 'row',
    alignItems: 'center'
  }
})
