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

import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { Typography } from '../Typography/Typography'
import { htmlAttrs } from '../utils/htmlAttrs'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { getTokensPropType } from '../utils/props/tokens'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { transformGradient } from '../utils/transformGradient'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs, viewProps])

const selectWrapperStyles = (left, top) => {
  const offset = left ?? top
  return {
    position: offset != null ? 'absolute' : 'relative',
    ...(left != null ? { left } : {}),
    ...(top != null ? { top } : {})
  }
}

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

const selectContainerRadius = ({
  borderRadius,
  borderRadiusBottomLeft,
  borderRadiusBottomRight
}) => ({
  borderRadius,
  ...(borderRadiusBottomLeft != null ? { borderBottomLeftRadius: borderRadiusBottomLeft } : {}),
  ...(borderRadiusBottomRight != null ? { borderBottomRightRadius: borderRadiusBottomRight } : {})
})

const selectBoxShadow = ({
  boxShadowPaddingTop,
  boxShadowPaddingRight,
  boxShadowPaddingBottom,
  boxShadowPaddingLeft,
  boxShadowColor
}) =>
  Platform.select({
    web: {
      boxShadow: `${boxShadowPaddingTop}px ${boxShadowPaddingRight}px ${boxShadowPaddingBottom}px ${boxShadowPaddingLeft}px ${boxShadowColor}`
    },
    default: {
      shadowColor: boxShadowColor,
      shadowOffset: { width: boxShadowPaddingTop, height: boxShadowPaddingRight },
      shadowRadius: boxShadowPaddingBottom
    }
  })

/**
 * The web curve is a quarter circle drawn with a bottom-left radius of 100%. Native
 * approximates the fold with a radius equal to the curve width.
 */
const selectCurveStyles = ({ curveBackgroundColor, curveWidth, curveHeight, curveMarginTop }) => ({
  backgroundColor: curveBackgroundColor,
  width: curveWidth,
  height: curveHeight,
  marginTop: -curveMarginTop,
  ...Platform.select({
    web: { borderBottomLeftRadius: '100%' },
    default: { borderBottomLeftRadius: curveWidth }
  })
})

const selectCurveAfterStyles = ({
  curveAfterBackgroundColor,
  curveAfterWidth,
  curveAfterHeight,
  curveAfterRadius
}) => ({
  backgroundColor: curveAfterBackgroundColor,
  width: curveAfterWidth,
  height: curveAfterHeight,
  borderBottomLeftRadius: curveAfterRadius
})

const getTypographyTokens = ({ fontColor, fontName, fontWeight, fontSize, lineHeight }) => ({
  color: fontColor,
  fontName,
  fontWeight,
  fontSize,
  lineHeight
})

/**
 * The offer variant is driven by a gradient. On web it is a CSS background string; on native the
 * gradient is drawn by the consumer-supplied customGradient render prop, so no background is set.
 */
const selectBackground = ({ gradient, backgroundColor }) => {
  if (!gradient) return { backgroundColor }
  return Platform.OS === 'web' ? { background: transformGradient(gradient) } : {}
}

/**
 * Converts a CSS gradient angle (degrees; 0 = toward the top, increasing clockwise) into the
 * { start, end } unit-square points expo-linear-gradient expects, so native renders the same
 * direction transformGradient emits on web. start/end sit on either side of the square's
 * center (0.5, 0.5), each offset by half the unit direction vector.
 */
const angleToStartEnd = (angle) => {
  const radians = (angle * Math.PI) / 180
  const center = 0.5
  const halfX = Math.sin(radians) / 2
  const halfY = -Math.cos(radians) / 2
  return {
    start: { x: center - halfX, y: center - halfY },
    end: { x: center + halfX, y: center + halfY }
  }
}

/**
 * Returns the { start, end, colors } descriptor passed to the customGradient render prop on
 * native, or null on web / when there is no gradient (web renders it as a CSS background).
 * The direction is derived from the token angle so it matches the web gradient.
 */
const selectGradientDescriptor = (gradient) => {
  if (!gradient || Platform.OS === 'web') return null
  const { start, end } = angleToStartEnd(gradient.angle)
  const [startStop, endStop] = gradient.stops
  return {
    colors: [startStop.color, endStop.color],
    start,
    end
  }
}

export const Ribbon = React.forwardRef(
  ({ children, tokens, left, top, variant = {}, customGradient, ...rest }, ref) => {
    const themeTokens = useThemeTokens('Ribbon', tokens, { ...variant })
    const gradient = selectGradientDescriptor(themeTokens.gradient)
    const containerStyle = [
      staticStyles.container,
      selectContainerPadding(themeTokens),
      selectContainerRadius(themeTokens),
      selectBoxShadow(themeTokens),
      staticStyles.shadow,
      selectBackground(themeTokens)
    ]

    const content = (
      <Typography variant={{ bold: true, inverse: true }} tokens={getTypographyTokens(themeTokens)}>
        {children}
      </Typography>
    )

    const container =
      gradient && typeof customGradient === 'function' ? (
        customGradient(gradient, containerStyle, content)
      ) : (
        <View style={containerStyle}>{content}</View>
      )

    return (
      <View
        ref={ref}
        style={[staticStyles.wrapper, selectWrapperStyles(left, top)]}
        {...selectProps(rest)}
      >
        {container}
        <View
          testID={rest.testID && `${rest.testID}-curve`}
          style={[staticStyles.curve, selectCurveStyles(themeTokens)]}
        >
          <View style={[staticStyles.curveAfter, selectCurveAfterStyles(themeTokens)]} />
        </View>
      </View>
    )
  }
)

Ribbon.displayName = 'Ribbon'

Ribbon.propTypes = {
  ...selectedSystemPropTypes,
  /** Overrides the component's theme tokens */
  tokens: getTokensPropType('Ribbon'),
  /** Selects a purpose variant: offer, default, editorial, or subtle */
  variant: variantProp.propType,
  /** Content rendered inside the ribbon */
  children: PropTypes.node,
  /** Sets the left offset (triggers absolute positioning) */
  left: PropTypes.number,
  /** Sets the top offset (triggers absolute positioning) */
  top: PropTypes.number,
  /** Render function that draws the offer gradient on native (receives the gradient descriptor, container style, and content) */
  customGradient: PropTypes.func
}

const staticStyles = StyleSheet.create({
  wrapper: {
    zIndex: 1,
    ...Platform.select({
      web: { width: 'fit-content' },
      default: { alignSelf: 'flex-start' }
    })
  },
  container: {
    display: 'flex',
    justifyContent: 'center'
  },
  shadow: Platform.select({
    web: {},
    default: { shadowOpacity: 1 }
  }),
  curve: {
    position: 'relative',
    zIndex: -1,
    overflow: 'hidden'
  },
  curveAfter: {
    position: 'absolute',
    top: 0,
    left: 0
  }
})
