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

import { LabelContent } from '../InputLabel/LabelContent'
import { RadioButton, selectRadioButtonTokens } from './RadioButton'
import { applyShadowToken, applyTextStyles } from '../ThemeProvider/utils/styles'
import { useThemeTokensCallback } from '../ThemeProvider/useThemeTokens'
import { a11yProps } from '../utils/props/a11yProps'
import { focusHandlerProps } from '../utils/props/handlerProps'
import { getTokensPropType } from '../utils/props/tokens'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { useInputValue } from '../utils/input'
import { useUniqueId } from '../utils/useUniqueId'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { StackView } from '../StackView/StackView'

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

const selectContainerStyles = ({
  containerBackgroundColor,
  containerBorderRadius,
  containerOpacity,
  containerPaddingBottom,
  containerPaddingLeft,
  containerPaddingRight,
  containerPaddingTop,
  containerShadow
}) => ({
  backgroundColor: containerBackgroundColor,
  borderRadius: containerBorderRadius,
  opacity: containerOpacity,
  paddingBottom: containerPaddingBottom,
  paddingLeft: containerPaddingLeft,
  paddingRight: containerPaddingRight,
  paddingTop: containerPaddingTop,
  ...applyShadowToken(containerShadow)
})
const selectDescriptionStyles = ({
  containerPaddingLeft = 0,
  descriptionFontSize,
  descriptionLineHeight,
  descriptionMarginLeft,
  descriptionFontName,
  descriptionFontWeight,
  inputSize,
  fontColor,
  labelMarginLeft = 0
}) => ({
  marginLeft: descriptionMarginLeft ?? containerPaddingLeft + inputSize + labelMarginLeft,
  color: fontColor,
  ...applyTextStyles({
    fontSize: descriptionFontSize,
    lineHeight: descriptionLineHeight,
    fontName: descriptionFontName,
    fontWeight: descriptionFontWeight
  })
})
const selectLabelStyles = ({
  labelColor,
  labelFontName,
  labelFontSize,
  labelFontWeight,
  labelMarginLeft,
  labelLineHeight
}) =>
  applyTextStyles({
    color: labelColor,
    fontName: labelFontName,
    fontWeight: labelFontWeight,
    fontSize: labelFontSize,
    lineHeight: labelLineHeight,
    marginLeft: labelMarginLeft
  })

/**
 * Basic Radio component.
 *
 * ## Component API
 *
 * Use `label` prop to provide a label for the radio button. For a disabled `Radio` set the `inactive` prop to `true`.
 *
 * ### Controlled version
 *
 * If the radio button is controlled from outside, it needs to receive `checked` and `onChange` props.
 *
 * ### Uncontrolled version
 *
 * In case of uncontrolled radio button you can use `defaultChecked` prop to provide the initial value.
 * Whenever the radio button gets toggled, it calls the `onChange` callback with the new value (boolean).
 *
 * ### Using within forms
 *
 * You can pass `name` and `value` props if you need a particular radio button to be a part of a radio group
 * on a form.
 *
 * ### Validation
 *
 * You can mark a radio button as failing validation by setting the `error` prop to `true`.
 *
 * ## A11y guidelines
 *
 * Radio component accepts all the common accessibility props, but also sets some defaults, including
 * accessibility role `'radio'` and accessibility state that depends on the other props (`checked`, `inactive`)
 * or the internal state in case of uncontrolled radio button.
 *
 */
export const Radio = React.forwardRef(
  (
    {
      checked,
      defaultChecked,
      description,
      error = false,
      id,
      inactive,
      label,
      name: inputName,
      onChange,
      tokens,
      value,
      variant,
      ...rest
    },
    ref
  ) => {
    const {
      currentValue: isChecked,
      setValue: setIsChecked,
      isControlled
    } = useInputValue(
      {
        value: checked,
        initialValue: defaultChecked,
        onChange
      },
      'useRadioValue'
    )
    const getTokens = useThemeTokensCallback('Radio', tokens, {
      checked: isChecked,
      inactive,
      error,
      ...variant
    })
    const handleChange = (event) => {
      if (!inactive && !isChecked) {
        setIsChecked(true, event)
      }
    }
    const handleKeyDown = (event) => {
      // The expected keyboard shortcut for activating a radio is the Space key
      if (event?.key === ' ') handleChange(event)
    }
    const uniqueId = useUniqueId('radio')
    const inputId = id ?? uniqueId

    const selectedProps = selectProps({
      accessibilityRole: 'radio',
      accessibilityState: { checked: isChecked, disabled: inactive },
      ...rest
    })

    return (
      <Pressable
        ref={ref}
        disabled={inactive}
        onKeyDown={handleKeyDown}
        onPress={handleChange}
        {...selectedProps}
        style={staticStyles.removeOutline}
      >
        {({ focused: focus, hovered: hover, pressed }) => {
          const stateTokens = getTokens({ focus, hover, pressed })
          const labelStyles = selectLabelStyles(stateTokens)
          const alignWithLabel = label
            ? [staticStyles.alignWithLabel, { height: labelStyles.lineHeight }]
            : null

          return (
            <StackView space={0}>
              <View style={[staticStyles.container, selectContainerStyles(stateTokens)]}>
                <View style={alignWithLabel}>
                  <RadioButton
                    tokens={selectRadioButtonTokens(stateTokens)}
                    isControlled={isControlled}
                    isChecked={isChecked}
                    inactive={inactive}
                    defaultChecked={defaultChecked}
                    inputId={inputId}
                    handleChange={handleChange}
                    name={inputName}
                    value={value}
                  />
                </View>
                {Boolean(label) && (
                  <Text style={labelStyles}>
                    <LabelContent forId={inputId}>{label}</LabelContent>
                  </Text>
                )}
              </View>
              {Boolean(description) && (
                <Text
                  style={selectDescriptionStyles({
                    ...getTokens(),
                    fontColor: labelStyles.color
                  })}
                >
                  {description}
                </Text>
              )}
            </StackView>
          )
        }}
      </Pressable>
    )
  }
)
Radio.displayName = 'Radio'

Radio.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Use `checked` for controlled Radio. For uncontrolled Radio, use the `defaultChecked` prop.
   */
  checked: PropTypes.bool,
  /**
   * Use `defaultChecked` to provide the initial value for an uncontrolled Radio.
   */
  defaultChecked: PropTypes.bool,
  /**
   * An optional radio button description.
   */
  description: PropTypes.string,
  /**
   * Radio button ID.
   */
  id: PropTypes.string,
  /**
   * Whether the corresponding input is disabled or active.
   */
  inactive: PropTypes.bool,
  /**
   * The label.
   */
  label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  /**
   * Associate this radio button with a group (set as the name attribute).
   */
  name: PropTypes.string,
  /**
   * Whether the underlying input triggered a validation error or not.
   */
  error: PropTypes.bool,
  /**
   * The value. Must be unique within the group.
   */
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
  /**
   * Callback called when a controlled radio button gets interacted with.
   */
  onChange: PropTypes.func,
  /**
   * Radio tokens.
   */
  tokens: getTokensPropType('Radio'),
  /**
   * Radio variant.
   */
  variant: variantProp.propType
}

const staticStyles = StyleSheet.create({
  container: { flexDirection: 'row', alignItems: 'center' },
  alignWithLabel: { alignSelf: 'flex-start', justifyContent: 'center' },
  removeOutline: { outlineStyle: 'none' }
})
