import React from 'react'
import PropTypes from 'prop-types'
import { Text, Platform, StyleSheet } from 'react-native'
import { a11yProps } from '../utils/props/a11yProps'
import { clickProps } from '../utils/props/clickProps'
import { getTokensPropType } from '../utils/props/tokens'
import { hrefAttrsProp } from '../utils/props/hrefAttrsProp'
import { linkProps } from '../utils/props/linkProps'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { contentfulProps } from '../utils/props/contentfulProps'
import { withLinkRouter } from '../utils/withLinkRouter'
import { resolvePressableTokens } from '../utils/pressability'
import { MobileIconTextContent } from './MobileIconTextContent'
import { InlinePressable } from './InlinePressable'
import { applyTextStyles, applyOuterBorder } from '../ThemeProvider/utils/styles'
import { useTheme } from '../ThemeProvider/useTheme'
import { iconComponentPropTypes } from '../Icon/Icon'
import { IconText } from '../Icon/IconText'
import { getTextScaleFactor, scaleLineHeightRatio } from '../utils/a11y/fontScaleConverter'
import { useFontScaleConverter } from '../utils/a11y/useFontScaleConverter'

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

const selectOuterBorderStyles = ({
  outerBorderColor,
  outerBorderWidth,
  outerBorderGap,
  borderRadius,
  outerBorderOutline
}) => ({
  outline: outerBorderOutline,
  ...applyOuterBorder({
    outerBorderColor,
    outerBorderWidth,
    outerBorderGap
  }),
  borderRadius
})

const selectTextStyles = ({ color, blockFontSize }) => {
  return {
    color,
    ...applyTextStyles({ blockFontSize }),
    ...Platform.select({
      web: {
        // TODO: https://github.com/telus/universal-design-system/issues/487
        transition: 'color 200ms'
      }
    })
  }
}

const selectBlockStyles = (
  { blockFontWeight, blockFontSize, blockLineHeight, blockFontName },
  themeOptions,
  converter
) =>
  applyTextStyles({
    lineHeight: scaleLineHeightRatio(converter, blockFontSize, blockLineHeight),
    fontWeight: blockFontWeight,
    fontSize: blockFontSize,
    fontName: blockFontName,
    themeOptions
  })

const selectDecorationStyles = ({ color, textLine, textLineStyle, alignSelf }) => ({
  color,
  textDecorationLine: textLine,
  textDecorationStyle: textLineStyle,
  alignSelf,

  ...Platform.select({
    web: {
      // TODO: https://github.com/telus/universal-design-system/issues/487
      transition: 'color 200ms'
    }
  })
})

const selectIconTokens = ({ color, iconSize, blockFontSize, iconTranslateX, iconTranslateY }) => {
  /**
   * These calculations were carried out using a set of linear equations to calculate that the
   * position of the icon "->"" is aligned to the first line of the tooltip text.
   * The base equation is: X/4 + Y/4 - 4 - |X - Y| = Z
   * where X = blockFontSize, Y = iconSize and Z = translateY
   */
  const translateY =
    iconTranslateY && iconTranslateY !== 0
      ? iconTranslateY
      : blockFontSize / 4 + iconSize / 4 - 4 - Math.abs(iconSize - blockFontSize)
  return {
    color,
    translateX: iconTranslateX,
    translateY,
    size: iconSize
  }
}

/**
 * On Android 14+ an inline icon must grow by the same non-linear factor as its text, not by the
 * linear `fontScale` `Icon` applies, or it outgrows the line box and the text is clipped (#7317).
 */
const selectInlineIconProps = ({
  converter,
  iconPosition,
  iconProps,
  iconTokens,
  blockFontSize
}) => {
  if (!converter || iconPosition !== 'inline' || iconProps.scalesWithText === false) return {}
  return {
    scalesWithText: false,
    tokens: { ...iconTokens, size: iconTokens.size * getTextScaleFactor(converter, blockFontSize) }
  }
}

/**
 * Renders a pressable text link, with optional icon. This is rendered as a block element
 * only (see 'Current Limitations'). On Web, renders an `<a>` anchor tag if `href` is provided.
 *
 * On native the label `Text` sets `accessibilityRole` to `none` so it doesn't inherit `link`;
 * otherwise Android exposes it as a nested clickable span that TalkBack activates instead of the
 * link, and `onPress` never fires (#7319).
 *
 * ## Current limitations
 *
 * - Not yet integrated with widely-used Telus navigation packages. This will probably
 *   require adding a `navigate` prop that aliases onPress so this can be passed to
 *   router's Link components' `Component` or `as` props but this needs testing
 *   as part of https://github.com/telus/universal-design-system/issues/24
 *
 * - Doesn't work inline e.g. in paragraphs. Support for inline links in native and
 *   web was implemented in https://github.com/telus/universal-design-system/pull/233
 *   but removed and will need to be restored at some point. When inline link support
 *   is restored, icons in inline links on Android will be slightly off-centre
 *   vertically (see )
 *
 * - Doesn't support :visited styles on web. Support for :visited styles was
 *   implemented in https://github.com/telus/universal-design-system/pull/234 but
 *   dropped in favour of investigating if a full-featured CSS-in-JS package could or
 *   should be used more widely (e.g. styled components)
 */
const LinkBaseInner = React.forwardRef(
  (
    {
      href,
      icon,
      iconPosition = icon ? 'left' : undefined,
      iconProps = {},
      variant,
      tokens = {},
      children,
      dataSet,
      disabled = false,
      useMeasuredMobileIconLayout = false,
      accessibilityRole = 'link',
      ...rawRest
    },
    ref
  ) => {
    const { onPress, ...props } = clickProps.toPressProps(rawRest)
    const { hrefAttrs, rest } = hrefAttrsProp.bundle(props)

    const selectedProps = selectProps({
      accessibilityRole,
      href: disabled ? undefined : href,
      onPress: disabled ? undefined : linkProps.handleHref({ href, onPress }),
      hrefAttrs,
      disabled,
      ...rest
    })

    const resolveLinkTokens = (pressState) =>
      resolvePressableTokens(tokens, pressState, { iconPosition, disabled })

    const defaultThemeTokens = resolveLinkTokens({})
    const hasIcon = Boolean(icon || defaultThemeTokens.icon)

    // On web, this makes focus rings wrap only the link, not the entire block
    const blockLeftStyle = Platform.OS === 'web' && staticStyles.blockLeft

    const { themeOptions } = useTheme()
    const converter = useFontScaleConverter()

    return (
      <InlinePressable
        {...selectedProps}
        dataSet={dataSet}
        inlineFlex={hasIcon}
        // assuming links without icons should be inline (even if they are long)
        ref={ref}
        style={(linkState) => {
          const themeTokens = resolveLinkTokens(linkState)
          const outerBorderStyles = selectOuterBorderStyles(themeTokens)
          const decorationStyles = selectDecorationStyles(themeTokens)
          const shouldUseMeasuredMobileContent =
            Platform.OS !== 'web' && useMeasuredMobileIconLayout

          return [
            outerBorderStyles,
            shouldUseMeasuredMobileContent
              ? staticStyles.measuredMobileOuterBorderCompensation
              : null,
            blockLeftStyle,
            decorationStyles,
            hasIcon && staticStyles.rowContainer
          ]
        }}
      >
        {(linkState) => {
          const themeTokens = resolveLinkTokens(linkState)
          const textStyles = selectTextStyles(themeTokens)
          const iconTokens = selectIconTokens(themeTokens)
          // TODO: may need to apply some smarter text inheritance here if inline to avoid native
          // issues like double-application of line heights breaking align-items: baseline
          const blockTextStyles = selectBlockStyles(themeTokens, themeOptions, converter)

          const IconComponent = icon || themeTokens.icon
          const { iconSpace } = themeTokens

          const isTextOnlyLink = !IconComponent && !icon && accessibilityRole === 'link'
          const adjustedIconSpace = Platform.OS !== 'web' && isTextOnlyLink ? 0 : iconSpace
          const shouldUseMeasuredMobileContent =
            Platform.OS !== 'web' && useMeasuredMobileIconLayout
          const textBaselineStyle = shouldUseMeasuredMobileContent ? null : staticStyles.baseline

          const linkTextContent = (
            <Text
              accessibilityRole={Platform.OS !== 'web' ? 'none' : undefined}
              style={[
                textStyles,
                blockTextStyles,
                textBaselineStyle,
                staticStyles.bubblePointerEvents
              ]}
            >
              {typeof children === 'function' ? children(linkState) : children}
            </Text>
          )

          const sharedIconProps = {
            ...iconProps,
            tokens: iconTokens,
            style: staticStyles.bubblePointerEvents,
            ...selectInlineIconProps({
              converter,
              iconPosition,
              iconProps,
              iconTokens,
              blockFontSize: themeTokens.blockFontSize
            })
          }

          if (shouldUseMeasuredMobileContent) {
            return (
              <MobileIconTextContent
                icon={IconComponent}
                iconPosition={iconPosition}
                space={adjustedIconSpace}
                iconProps={sharedIconProps}
              >
                {linkTextContent}
              </MobileIconTextContent>
            )
          }

          return (
            <IconText
              icon={IconComponent}
              iconPosition={iconPosition}
              space={adjustedIconSpace}
              iconProps={sharedIconProps}
            >
              {linkTextContent}
            </IconText>
          )
        }}
      </InlinePressable>
    )
  }
)
LinkBaseInner.displayName = 'LinkBase'

LinkBaseInner.propTypes = {
  ...selectedSystemPropTypes,
  tokens: getTokensPropType('Link'),
  /**
   * href for the Link
   */
  href: PropTypes.string,
  /**
   * AccessibilityRole for the link base
   */
  accessibilityRole: PropTypes.string,
  /**
   * Children nodes that can be added
   */
  variant: variantProp.propType,
  /**
   * Optional variant that may be passed down to the link's icon if there is one
   */
  iconProps: PropTypes.exact(iconComponentPropTypes),
  /**
   * A function component for an SVG icon to render inside the link. Inherits size and color from
   * the link and any Typography the link is nested inside.
   */
  icon: PropTypes.elementType,
  /**
   * When `icon` is provided, use `iconPosition` to place the Icon to the left, right or inline with Link.
   */
  iconPosition: PropTypes.oneOf(['left', 'right', 'inline']),
  /**
   * Native only. Aligns the link's icon using a measured layout instead of text baseline
   * alignment, and compensates for the outer border spacing. Has no effect on web.
   */
  useMeasuredMobileIconLayout: PropTypes.bool,
  /**
   * On Web if href is passed, React Native Web maps this object's props to
   * `rel`, `target` and `download` attrs.
   */
  hrefAttrs: PropTypes.shape(hrefAttrsProp.types),
  /**
   * When true, the link is non-interactive: it renders no navigable href and cannot be
   * focused or pressed.
   */
  disabled: PropTypes.bool
}

const staticStyles = StyleSheet.create({
  blockLeft: {
    alignSelf: 'flex-start'
  },
  rowContainer: {
    flexDirection: 'row',
    justifyContent: 'flex-start'
  },
  baseline: {
    alignSelf: 'baseline'
  },
  bubblePointerEvents: {
    ...Platform.select({
      web: {
        pointerEvents: 'none'
      }
    })
  },
  measuredMobileOuterBorderCompensation: {
    ...(Platform.OS !== 'web' && {
      marginHorizontal: 2,
      marginVertical: 2,
      paddingHorizontal: Platform.OS === 'android' ? 2 : 0,
      paddingVertical: Platform.OS === 'android' ? 2 : 0
    })
  }
})

export const LinkBase = withLinkRouter(LinkBaseInner)
