import React from 'react'
import PropTypes from 'prop-types'
import { View, StyleSheet, Platform } from 'react-native'
import { viewports } from '@telus-uds/system-constants'
import { Spacer } from '../Spacer/Spacer'
import { StackView } from '../StackView/StackView'
import { Typography } from '../Typography/Typography'
import { useViewport } from '../ViewportProvider/useViewport'
import { useTheme } from '../ThemeProvider/useTheme'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { useResponsiveThemeTokens } from '../ThemeProvider/useResponsiveThemeTokens'
import {
  createMediaQueryStyles,
  StyleSheet as UDSStyleSheet
} from '../utils/ssr-media-query/ssr-media-query'
import { applyTextStyles } from '../ThemeProvider/utils/styles'
import { getTokensPropType } from '../utils/props/tokens'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { a11yProps } from '../utils/props/a11yProps'
import { viewProps } from '../utils/props/viewProps'
import { Segment } from './Segment'
import { useCountdown } from './useCountdown'
import { dictionaryContentShape } from './types'
import { transformGradient } from '../utils/transformGradient'
import { variantProp } from '../utils/props/variantProp'

const DAYS = 'days'
const HOURS = 'hours'
const MINUTES = 'minutes'
const SECONDS = 'seconds'

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

const getLabelTokens = (themeTokens) => ({
  color: themeTokens.labelBorderColor,
  fontSize: themeTokens.labelFontSize,
  fontWeight: themeTokens.labelFontWeight,
  lineHeight: themeTokens.labelLineHeight
})

/**
 * Wraps RNMQ ids in the `dataSet` shape components use to expose them as `data-media` on web.
 * Returns nothing when there are no ids, so the attribute is omitted rather than rendered empty.
 *
 * @param {string} [mediaIds] - space-separated `rnmq-*` ids from `StyleSheet.create`
 * @returns {{ dataSet: { media: string } } | undefined}
 *
 * @example
 * <Typography {...mediaDataSet(ids.mainText)} />
 */
const mediaDataSet = (mediaIds) => (mediaIds ? { dataSet: { media: mediaIds } } : undefined)

const getMainTextTokens = ({
  labelBorderColor,
  textTimerFontWeight,
  textFontSize,
  textLineHeight,
  textTimerFontName
}) => ({
  color: labelBorderColor,
  lineHeight: textLineHeight,
  fontWeight: textTimerFontWeight,
  fontSize: textFontSize,
  fontName: textTimerFontName
})

export const Countdown = React.forwardRef(
  (
    {
      copy = 'en',
      targetTime,
      tokens,
      variant = {},
      units = [DAYS, HOURS, MINUTES, SECONDS],
      ...rest
    },
    ref
  ) => {
    const [days, hours, minutes, seconds] = useCountdown(targetTime)
    const viewport = useViewport()
    const { feature, large, noDivider, inverse } = variant

    const { themeOptions } = useTheme()
    const { enableMediaQueryStyleSheet } = themeOptions

    const useTokens = enableMediaQueryStyleSheet ? useResponsiveThemeTokens : useThemeTokens
    const resolvedTokens = useTokens(
      'Countdown',
      tokens,
      variant,
      !enableMediaQueryStyleSheet && { viewport }
    )
    // Under RNMQ `resolvedTokens` is a viewport map — read through `currentTokens`, never off it; only the reduce consumes the map (#7217; fallback removable via #7241)
    const currentTokens = enableMediaQueryStyleSheet
      ? resolvedTokens[viewport] ?? resolvedTokens[viewports.keys[0]]
      : resolvedTokens

    const semanticGradient =
      feature && !inverse && currentTokens.containerGradient
        ? transformGradient(currentTokens.containerGradient)
        : null

    const segmentFontSize = currentTokens.textFontSize
    const mainTextTokens = getMainTextTokens(currentTokens)
    const labelTokens = getLabelTokens(currentTokens)

    let labelStyles
    let labelMediaIds
    let mainTextStyles
    let mainTextMediaIds

    let containerMediaIds
    if (enableMediaQueryStyleSheet) {
      const { labelByViewport, mainTextByViewport, containerByViewport } = Object.entries(
        resolvedTokens
      ).reduce(
        (acc, [vp, viewportTokens]) => {
          acc.labelByViewport[vp] = {
            fontSize: viewportTokens.labelFontSize,
            lineHeight: viewportTokens.labelLineHeight
          }
          acc.mainTextByViewport[vp] = {
            fontSize: viewportTokens.textFontSize,
            lineHeight: viewportTokens.textLineHeight
          }
          // Media rules must name real CSS properties — `paddingVertical` serialises to `padding-vertical` (#7217)
          acc.containerByViewport[vp] = {
            paddingTop: viewportTokens.containerPaddingBottomTop,
            paddingBottom: viewportTokens.containerPaddingBottomTop,
            paddingLeft: viewportTokens.containerPaddingLeftRight,
            paddingRight: viewportTokens.containerPaddingLeftRight
          }
          return acc
        },
        { labelByViewport: {}, mainTextByViewport: {}, containerByViewport: {} }
      )

      const { ids, styles } = UDSStyleSheet.create({
        label: { ...labelTokens, ...createMediaQueryStyles(labelByViewport) },
        mainText: { ...mainTextTokens, ...createMediaQueryStyles(mainTextByViewport) },
        container: feature ? createMediaQueryStyles(containerByViewport) : undefined
      })
      labelStyles = styles.label
      labelMediaIds = ids.label
      mainTextStyles = styles.mainText
      mainTextMediaIds = ids.mainText
      containerMediaIds = ids.container
    } else {
      labelStyles = labelTokens
      mainTextStyles = mainTextTokens
    }

    // Keyed by viewport so `Spacer` expresses the step in CSS and the `xs` server render stops reflowing on hydration (#7217)
    const divider =
      noDivider === true ? (
        <Spacer direction="row" space={feature || large ? { xs: 1, sm: 4 } : 1} />
      ) : (
        <Typography tokens={mainTextStyles} {...mediaDataSet(mainTextMediaIds)}>
          :
        </Typography>
      )

    const containerStyle = [
      staticStyles.container,
      (large || feature) && staticStyles.containerLarge,
      Platform.select({
        web: {
          display: 'inline-flex',
          width: 'fit-content',
          maxWidth: '100%'
        }
      }),
      feature && {
        borderRadius: currentTokens.containerBorderRadius,
        borderStyle: 'solid',
        borderWidth: currentTokens.containerBorderWidth,
        borderColor: currentTokens.containerBorderColor,
        justifyContent: 'center',
        paddingVertical: currentTokens.containerPaddingBottomTop,
        paddingHorizontal: currentTokens.containerPaddingLeftRight,
        ...Platform.select({
          web: semanticGradient
            ? {
                borderColor: 'transparent',
                background: `linear-gradient(#fff 0 0) padding-box, ${semanticGradient} border-box`
              }
            : null
        })
      }
    ]

    const segmentProps = {
      copy,
      segmentFontSize,
      labelTokens: labelStyles,
      numberTokens: mainTextStyles,
      labelMediaIds,
      mainTextMediaIds,
      variant
    }

    return (
      <View
        ref={ref}
        style={containerStyle}
        {...mediaDataSet(containerMediaIds)}
        {...selectProps(rest)}
        {...Platform.select({ web: { tabIndex: 0 } })}
        accessible
      >
        <StackView direction="row" space={1}>
          {units.map((unit, index) => {
            let number
            let labelKey
            switch (unit) {
              case DAYS:
                number = days
                labelKey = 'day'
                break
              case HOURS:
                number = hours
                labelKey = 'hour'
                break
              case MINUTES:
                number = minutes
                labelKey = 'minute'
                break
              case SECONDS:
                number = seconds
                labelKey = 'second'
                break
              default:
                return null
            }

            return (
              <React.Fragment key={unit}>
                <Segment
                  labelKey={labelKey}
                  number={number}
                  {...(unit === DAYS && { segmentWidth: String(number).length })}
                  {...segmentProps}
                  {...applyTextStyles(currentTokens)}
                />
                {index < units.length - 1 && divider}
              </React.Fragment>
            )
          })}
        </StackView>
      </View>
    )
  }
)

Countdown.displayName = 'Countdown'

const staticStyles = StyleSheet.create({
  container: {
    alignSelf: 'flex-start'
  },
  containerLarge: {
    flexGrow: 0,
    flexShrink: 0
  }
})

Countdown.propTypes = {
  ...selectedSystemPropTypes,
  tokens: getTokensPropType('Countdown'),
  /**
   * Copy language identifier (`'en'` or `'fr'`) or a dictionary instance (an object with
   * the following keys: days, day, hours, hour, minutes, minute, seconds, second)
   */
  copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), dictionaryContentShape]),
  /**
   * An instance of JavaScript `Date` object or a string parseable via `Date.parse()`
   * representing a point in the future to count down to.
   */
  targetTime: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
  /**
   * Selects an appearance variant. Supported keys: `feature`, `inverse`, `label`, `large`, `noDivider`.
   */
  variant: variantProp.propType,
  /**
   * An array of strings representing the units to display in the countdown.
   * Possible values: `'days'`, `'hours'`, `'minutes'`, `'seconds'`
   */
  units: PropTypes.arrayOf(PropTypes.oneOf([DAYS, HOURS, MINUTES, SECONDS]))
}
