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

import { CheckboxInput } from './CheckboxInput'
import { LabelContent } from '../InputLabel/LabelContent'
import { Feedback } from '../Feedback/Feedback'
import { StackView } from '../StackView/StackView'
import { applyShadowToken, applyTextStyles } from '../ThemeProvider/utils/styles'
import { useTheme } from '../ThemeProvider/useTheme'
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 { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { useUniqueId } from '../utils/useUniqueId'

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

const selectInputStyles = (
  {
    iconBackgroundColor,
    inputBorderColor,
    inputBorderRadius,
    inputBorderWidth,
    inputBackgroundColor,
    inputHeight,
    inputOutlineColor,
    inputOutlineWidth,
    inputOutlineOffset,
    inputShadow,
    inputWidth
  },
  isChecked
) => ({
  borderColor: inputBorderColor,
  borderWidth: inputBorderWidth,
  borderRadius: inputBorderRadius,
  backgroundColor: isChecked ? iconBackgroundColor : inputBackgroundColor,
  height: inputHeight,
  width: inputWidth,
  ...applyShadowToken(inputShadow),
  ...Platform.select({
    web: {
      outlineStyle: 'solid',
      outlineColor: inputOutlineColor,
      outlineWidth: inputOutlineWidth,
      outlineOffset: inputOutlineOffset
    }
  })
})

const selectDescriptionStyles = ({
  containerPaddingLeft = 0,
  descriptionFontSize,
  descriptionLineHeight,
  descriptionMarginLeft,
  descriptionFontName,
  descriptionFontWeight,
  descriptionFontColor,
  inputWidth,
  labelMarginLeft = 0
}) => ({
  marginLeft: descriptionMarginLeft ?? containerPaddingLeft + inputWidth + labelMarginLeft,
  ...applyTextStyles({
    color: descriptionFontColor,
    fontSize: descriptionFontSize,
    lineHeight: descriptionLineHeight,
    fontName: descriptionFontName,
    fontWeight: descriptionFontWeight
  })
})

const selectLabelStyles = (
  { labelColor, labelFontName, labelFontSize, labelFontWeight, labelMarginLeft, labelLineHeight },
  themeOptions
) => ({
  marginLeft: labelMarginLeft,
  ...applyTextStyles({
    color: labelColor,
    fontName: labelFontName,
    fontWeight: labelFontWeight,
    fontSize: labelFontSize,
    lineHeight: labelLineHeight,
    themeOptions
  })
})
const selectIconTokens = ({ icon, iconColor, iconSize }) => ({
  icon,
  color: iconColor,
  size: iconSize
})

const selectFeedbackTokens = ({ feedbackMarginBottom, feedbackMarginTop, feedbackPosition }) => ({
  feedbackPosition,
  feedbackMarginBottom,
  feedbackMarginTop
})

const selectPressableStyles = ({
  padding,
  paddingLeft,
  paddingRight,
  paddingTop,
  paddingBottom
}) => ({
  padding,
  paddingLeft,
  paddingRight,
  paddingTop,
  paddingBottom
})

const selectContainerStyles = ({ iconContainerHeight, iconContainerWidth }) => ({
  height: iconContainerHeight,
  width: iconContainerWidth
})

const selectContainerAlignmentStyles = ({ containerAlignItems }) => ({
  alignItems: containerAlignItems ?? 'center'
})

/**
 * Basic Checkbox component.
 *
 * ## Component API
 *
 * Use `label` prop to provide a label for the checkbox.
 * For a disabled `Checkbox` set the `inactive` prop to `true`.
 *
 * ### Controlled version
 *
 * If the checkbox is controlled from outside, it needs to receive `checked` and `onChange` props.
 *
 * ### Uncontrolled version
 *
 * In case of uncontrolled checkbox you can use `defaultChecked` prop to provide the initial value.
 * Whenever the checkbox 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 checkbox to be a part of a group of
 * checkboxes on a form.
 *
 * ### Validation and feedback
 *
 * You can use the `feedback` prop to provide a comment related to the checkbox element. If the comment
 * is related to a validation error, the checkbox and the feedback can be styled accordingly by setting
 * the `error` prop to `true` (can also be done without feedback).
 *
 * ## A11y guidelines
 *
 * Checkbox component accepts all the common accessibility props, but also sets some defaults, including
 * accessibility role `'checkbox'` and accessibility state that depends on the other props (`checked`, `inactive`)
 * or the internal state in case of uncontrolled checkbox.
 *
 */
export const Checkbox = React.forwardRef(
  (
    {
      checked,
      defaultChecked,
      description,
      error = false,
      feedback,
      id,
      inactive,
      label,
      name,
      onChange,
      tokens,
      value,
      variant,
      testID,
      ...rest
    },
    ref
  ) => {
    const {
      currentValue: isChecked,
      setValue: setIsChecked,
      isControlled
    } = useInputValue(
      {
        value: checked,
        initialValue: defaultChecked,
        onChange
      },
      'useCheckboxValue'
    )
    const getTokens = useThemeTokensCallback('Checkbox', tokens, {
      checked: isChecked,
      inactive,
      error,
      ...variant
    })
    const defaultTokens = getTokens()
    const { feedbackMarginBottom, feedbackMarginTop, feedbackPosition } =
      selectFeedbackTokens(defaultTokens)
    const styles = StyleSheet.create({
      feedbackContainer: { marginTop: feedbackMarginTop, marginBottom: feedbackMarginBottom }
    })
    const handleChange = (event) => {
      if (!inactive) {
        setIsChecked(!isChecked, event)
      }
    }
    const handleKeyDown = (event) => {
      // The expected keyboard shortcut for activating a checkbox is the Space key
      if (event?.key === ' ') handleChange(event)
    }
    const uniqueId = useUniqueId('checkbox')
    const inputId = id ?? uniqueId
    const { themeOptions } = useTheme()

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

    return (
      <View style={staticStyles.wrapper} ref={ref}>
        <StackView direction={feedbackPosition === 'top' ? 'column-reverse' : 'column'} space={0}>
          <Pressable
            testID={testID && `${testID}-pressable`}
            disabled={inactive}
            onKeyDown={handleKeyDown}
            onPress={handleChange}
            {...selectedProps}
            style={[staticStyles.removeOutline, selectPressableStyles(defaultTokens)]}
          >
            {({ focused: focus, hovered: hover, pressed }) => {
              const { icon: IconComponent, ...stateTokens } = getTokens({ focus, hover, pressed })
              const iconTokens = selectIconTokens(stateTokens)
              const labelStyles = selectLabelStyles(stateTokens, themeOptions)

              return (
                <StackView space={0}>
                  <View
                    testID={testID && `${testID}-container`}
                    style={[staticStyles.container, selectContainerAlignmentStyles(stateTokens)]}
                  >
                    <View style={[staticStyles.iconContainer, selectContainerStyles(stateTokens)]}>
                      <View
                        style={[
                          staticStyles.defaultInputStyles,
                          selectInputStyles(stateTokens, isChecked)
                        ]}
                        testID="Checkbox-Input"
                      >
                        {/* Add a real input on Web, skip on native platforms */}
                        <CheckboxInput
                          checked={isChecked}
                          defaultChecked={defaultChecked}
                          disabled={inactive}
                          id={inputId}
                          isControlled={isControlled}
                          name={name}
                          value={value}
                        />
                        {isChecked && IconComponent && (
                          <View testID="Checkbox-Icon">
                            <IconComponent {...iconTokens} />
                          </View>
                        )}
                      </View>
                    </View>
                    {Boolean(label) && (
                      <Text style={labelStyles}>
                        <LabelContent forId={inputId}>{label}</LabelContent>
                      </Text>
                    )}
                  </View>
                  {Boolean(description) && (
                    <Text style={selectDescriptionStyles(defaultTokens)}>{description}</Text>
                  )}
                </StackView>
              )
            }}
          </Pressable>
          {Boolean(feedback) && (
            <View style={styles.feedbackContainer}>
              <Feedback
                title={feedback}
                variant={{ icon: error === true }}
                validation={error === true ? 'error' : undefined}
              />
            </View>
          )}
        </StackView>
      </View>
    )
  }
)
Checkbox.displayName = 'Checkbox'

Checkbox.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Use `checked` for controlled Checkbox. For uncontrolled Checkbox, use the `defaultChecked` prop.
   */
  checked: PropTypes.bool,
  /**
   * Use `defaultChecked` to provide the initial value for an uncontrolled Checkbox.
   */
  defaultChecked: PropTypes.bool,
  /**
   * A detailed description of related validation error or additional instructions (depending on the `error` prop).
   */
  feedback: PropTypes.string,
  /**
   * Checkbox ID.
   */
  id: PropTypes.string,
  /**
   * Whether the corresponding input is disabled or active.
   */
  inactive: PropTypes.bool,
  /**
   * The label.
   */
  label: PropTypes.string,
  /**
   * Associate this checkbox 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 checkbox gets interacted with.
   */
  onChange: PropTypes.func,
  /**
   * Checkbox tokens.
   */
  tokens: getTokensPropType('Checkbox'),
  /**
   * Checkbox variant.
   */
  variant: variantProp.propType,
  /**
   * An optional Checkbox description.
   */
  description: PropTypes.string,
  /**
   * A identifier for testing purposes.
   */
  testID: PropTypes.string
}

const staticStyles = StyleSheet.create({
  wrapper: { backgroundColor: 'transparent' },
  container: { flexDirection: 'row' },
  defaultInputStyles: { alignItems: 'center', justifyContent: 'center' },
  iconContainer: { display: 'flex', justifyContent: 'center', alignItems: 'center' },
  removeOutline: { outlineStyle: 'none' }
})
