import React from 'react'
import PropTypes from 'prop-types'
import { Pressable, Platform, View, StyleSheet } from 'react-native'
import { useViewport } from '../ViewportProvider/useViewport'
import { applyOuterBorder } from '../ThemeProvider/utils/styles'
import { validateThemeTokens } from '../ThemeProvider/utils/theme-tokens'
import { a11yProps } from '../utils/props/a11yProps'
import { clickProps } from '../utils/props/clickProps'
import { focusHandlerProps } from '../utils/props/handlerProps'
import { getTokenNames, getTokensSetPropType, selectTokens } from '../utils/props/tokens'
import { linkProps } from '../utils/props/linkProps'
import { resolvePressableState, resolvePressableTokens } from '../utils/pressability'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { withLinkRouter } from '../utils/withLinkRouter'
import { CardBase, fullBleedContentPropTypes } from './CardBase'

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

const selectFocusOverlayContainerStyles = (tokens) => {
  const { flex, minWidth, marginTop, marginBottom, marginLeft, marginRight } = tokens

  return {
    flex: flex || 1,
    minWidth: minWidth || 0,
    marginTop,
    marginBottom,
    marginLeft,
    marginRight
  }
}

const selectFocusBorderStyles = (tokens) => {
  const { borderWidth, borderColor, borderRadius } = tokens

  return {
    borderWidth,
    borderColor,
    borderRadius: borderRadius || 0
  }
}

const FocusBorderOverlay = ({ tokens, pressableState, children }) => {
  const { borderWidth = 0 } = tokens
  const showFocusBorder = pressableState.focused && borderWidth > 0

  return (
    <View
      style={[
        staticStyles.focusOverlayContainer,
        selectFocusOverlayContainerStyles(tokens),
        Platform.OS === 'web' && staticStyles.webOutlineNone
      ]}
    >
      {children}
      {showFocusBorder && (
        <View
          style={[staticStyles.focusBorder, selectFocusBorderStyles(tokens)]}
          accessible={false}
          importantForAccessibility="no"
        />
      )}
    </View>
  )
}

FocusBorderOverlay.propTypes = {
  tokens: PropTypes.shape({
    flex: PropTypes.number,
    minWidth: PropTypes.number,
    marginTop: PropTypes.number,
    marginBottom: PropTypes.number,
    marginLeft: PropTypes.number,
    marginRight: PropTypes.number,
    borderColor: PropTypes.string,
    borderWidth: PropTypes.number,
    borderRadius: PropTypes.number
  }).isRequired,
  pressableState: PropTypes.shape({
    focused: PropTypes.bool
  }).isRequired,
  children: PropTypes.node
}

// Derived from the `Card` schema rather than copied from it: a hand-maintained duplicate drifted
// behind `backgroundGradient`, `maxHeight` and `overflowY`, and the resulting `PropTypes.exact`
// failure fired on every interactive card. Outer border tokens are not in any component schema.
// TODO: centralise common token sets like these as part of
// https://github.com/telus/universal-design-system/issues/782
const tokenKeys = [
  ...getTokenNames('Card'),
  'outerBorderColor',
  'outerBorderWidth',
  'outerBorderGap'
]
export const selectPressableCardTokens = (tokens) =>
  Object.fromEntries(tokenKeys.map((key) => [key, tokens[key]]))

/**
 * A pressable themeless base component that handles pressable states and passes tokens
 * based on these to an outer border and a base Card component. Not intended to be used in
 * apps or sites directly: build themed components on top of this.
 */
const PressableCardBaseInner = React.forwardRef(
  (
    {
      children,
      tokens,
      checked,
      validation,
      inactive,
      href,
      hrefAttrs,
      dataSet,
      backgroundImage,
      fullBleedContent,
      accessibilityRole = href ? 'link' : undefined,
      ...rawRest
    },
    ref
  ) => {
    const { onPress, ...rest } = clickProps.toPressProps(rawRest)
    const viewport = useViewport()
    const additionalState = { checked, validation, inactive, viewport }

    const getCardState = (pressableState) => resolvePressableState(pressableState, additionalState)
    const getTokens = (pressableState) =>
      validateThemeTokens(
        resolvePressableTokens(tokens, pressableState, additionalState),
        getTokensSetPropType(tokenKeys, { partial: true, allowFunction: true }),
        'PressableCard'
      )

    const getCardTokens = (pressableState) => {
      const allTokens = getTokens(pressableState)
      const cardTokens = selectTokens('Card', allTokens)

      // Handle focus border transparency to avoid double borders
      if (pressableState.focused && allTokens.borderWidth > 0) {
        const result = {
          ...cardTokens,
          borderColor: 'transparent'
        }

        // Also handle backgroundImage for interactive states
        if (backgroundImage) {
          const { hovered, pressed, focused } = pressableState || {}
          const isInteractiveState = hovered || pressed || focused

          if (!isInteractiveState) {
            const { backgroundColor, ...restTokens } = result
            return restTokens
          }
        }

        return result
      }

      // Handle backgroundImage when not in focus state
      if (backgroundImage) {
        const { hovered, pressed, focused } = pressableState || {}
        const isInteractiveState = hovered || pressed || focused

        if (!isInteractiveState) {
          const { backgroundColor, ...restTokens } = cardTokens
          return restTokens
        }
      }

      return cardTokens
    }

    const getOuterBorderStyle = (pressableState) => {
      const {
        flex,
        minWidth,
        marginTop,
        marginBottom,
        marginLeft,
        marginRight,
        outerBorderColor,
        outerBorderGap = 0,
        outerBorderWidth = 0,
        borderRadius
      } = getTokens(pressableState)
      return {
        flex,
        minWidth: minWidth + outerBorderGap + outerBorderWidth,
        marginTop,
        marginBottom,
        marginLeft,
        marginRight,
        ...applyOuterBorder({ outerBorderColor, outerBorderGap, outerBorderWidth, borderRadius }),
        ...Platform.select({
          web: {
            outlineStyle: 'none',
            // Reset default <button> centering behavior when react-native-web renders Pressable as a button
            alignItems: 'stretch',
            textAlign: 'inherit'
          }
        })
      }
    }

    const handleChange = linkProps.handleHref({ href, onPress })
    const handleKeyDown = (event) => {
      // The expected keyboard shortcut for selecting a focused item is the Space key
      if (event?.key === ' ') handleChange?.(event)
    }

    const [hovered, setHovered] = React.useState(false)
    const [focused, setFocused] = React.useState(false)
    const [pressed, setPressed] = React.useState(false)

    if (Platform.OS === 'web' && href) {
      return (
        <View
          ref={ref}
          style={getOuterBorderStyle({ pressed, focused, hovered })}
          dataSet={dataSet}
          onMouseEnter={() => setHovered(true)}
          onMouseLeave={() => setHovered(false)}
          {...selectProps({ ...rest })}
        >
          <a
            href={href}
            tabIndex={0}
            onClick={(event) => {
              setPressed(false)
              handleChange?.(event)
            }}
            onKeyDown={(event) => {
              if (event.key === ' ') {
                setPressed(true)
                handleKeyDown(event)
              }
            }}
            onKeyUp={(event) => {
              if (event.key === ' ') setPressed(false)
            }}
            onMouseDown={() => setPressed(true)}
            onMouseUp={() => setPressed(false)}
            onFocus={() => setFocused(true)}
            onBlur={() => {
              setFocused(false)
              setPressed(false)
            }}
            style={staticStyles.linkContainer}
            {...(hrefAttrs || {})}
            role={accessibilityRole}
          >
            <CardBase
              tokens={getCardTokens({ pressed, focused, hovered })}
              backgroundImage={backgroundImage}
              fullBleedContent={fullBleedContent}
            >
              {typeof children === 'function'
                ? children(getCardState({ pressed, focused, hovered }))
                : children}
            </CardBase>
          </a>
        </View>
      )
    }

    return (
      <Pressable
        ref={ref}
        href={href}
        onPress={handleChange}
        onKeyDown={handleKeyDown}
        hrefAttrs={hrefAttrs}
        style={getOuterBorderStyle}
        dataSet={dataSet}
        {...selectProps({ ...rest, accessibilityRole })}
      >
        {(pressableState) => (
          <FocusBorderOverlay tokens={getTokens(pressableState)} pressableState={pressableState}>
            <CardBase
              tokens={getCardTokens(pressableState)}
              backgroundImage={backgroundImage}
              fullBleedContent={fullBleedContent}
            >
              {typeof children === 'function' ? children(getCardState(pressableState)) : children}
            </CardBase>
          </FocusBorderOverlay>
        )}
      </Pressable>
    )
  }
)

const staticStyles = StyleSheet.create({
  container: {
    flex: 1,
    display: 'flex'
  },
  linkContainer: {
    flex: 1,
    display: 'flex',
    alignItems: 'stretch',
    justifyContent: 'flex-start',
    textDecorationLine: 'none'
  },
  focusOverlayContainer: {
    position: 'relative'
  },
  webOutlineNone: {
    outlineStyle: 'none'
  },
  focusBorder: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    pointerEvents: 'none'
  }
})

PressableCardBaseInner.displayName = 'PressableCardBase'

PressableCardBaseInner.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Content of the card. A render function receives the card's current interaction state
   * (`pressed`, `focused`, `hovered`).
   */
  children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
  tokens: getTokensSetPropType(tokenKeys, { partial: true, allowFunction: true }),
  variant: variantProp.propType,
  /**
   * Apply background image to the card.
   */
  backgroundImage: PropTypes.shape({
    // The image src is either a URI string or a number (when a local image src is bundled in IOS or Android app)
    // src is an object when used responsively to provide different image sources for different screen sizes
    src: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]).isRequired,
    alt: PropTypes.string,
    resizeMode: PropTypes.oneOfType([
      PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center']),
      PropTypes.object
    ]),
    position: PropTypes.oneOfType([
      PropTypes.oneOf(['bottom', 'left', 'right', 'top']),
      PropTypes.object
    ]),
    align: PropTypes.oneOfType([
      PropTypes.oneOf(['start', 'end', 'center', 'stretch']),
      PropTypes.object
    ])
  }),
  /**
   * Content rendered flush to one edge of the card, without the card's padding. `position` and
   * `align` may be set per viewport.
   */
  fullBleedContent: fullBleedContentPropTypes
}

export const PressableCardBase = withLinkRouter(PressableCardBaseInner)
