import { useViewportWidth } from '@xstyled/styled-components'
import { FC } from 'react'
import { StyledProps } from 'styled-components'

import { Screen } from '../theme'
import { AnyNumber, AnyString } from '../utils'
import { th, useTheme } from './theme'
import { ChildrenProp } from './types'

type ScreenWidth = Screen | AnyNumber | AnyString

export type RenderOnScreenProps = {
  /** Breakpoint to compare viewport width against. */
  maxWidth?: Screen
  /** Breakpoint to compare viewport width against. */
  minWidth?: Screen
}

/** Returns CSS media string with appropriate min-width and max-width values. */
export const mediaBetween = (minWidth: ScreenWidth, maxWidth: ScreenWidth) => (props: StyledProps<unknown>) =>
  `@media (min-width: ${th.screen(minWidth)(props)}) and (max-width: ${th.screen(maxWidth)(props)})`

/** Returns CSS media string with appropriate max-width value */
export const mediaDown = (width: ScreenWidth) => (props: StyledProps<unknown>) => {
  return `@media (max-width: ${th.screen(width)(props)})`
}

/** Returns CSS media string with appropriate min-width value. */
export const mediaUp = (width: ScreenWidth) => (props: StyledProps<unknown>) =>
  `@media (min-width: ${th.screen(width)(props)})`

/**
 * Renders its content when viewport width is at or above a provided breakpoint.
 * Optionally, direction can be set to down, or below, the breakpoint.
 */
export const RenderOnScreen: FC<RenderOnScreenProps & ChildrenProp> = ({ children, maxWidth, minWidth }) => {
  if (minWidth && maxWidth) {
    return (
      <RenderBetween maxWidth={maxWidth} minWidth={minWidth}>
        {children}
      </RenderBetween>
    )
  }
  if (minWidth) {
    return <RenderOver width={minWidth}>{children}</RenderOver>
  }
  if (maxWidth) {
    return <RenderUnder width={maxWidth}>{children}</RenderUnder>
  }
  return <>{children}</>
}
const RenderBetween: FC<{ maxWidth: Screen; minWidth: Screen } & ChildrenProp> = ({ children, maxWidth, minWidth }) => {
  const isBetween = useBetween(minWidth, maxWidth)
  return isBetween ? <>{children}</> : null
}
const RenderOver: FC<{ width: Screen } & ChildrenProp> = ({ children, width }) => {
  const isOver = useUp(width)
  return isOver ? <>{children}</> : null
}
const RenderUnder: FC<{ width: Screen } & ChildrenProp> = ({ children, width }) => {
  const isUnder = useDown(width)
  return isUnder ? <>{children}</> : null
}

/** /!\ No need to export it, keep it internal */
/** Returns viewport width and provided screen width as a number. */
function useMedia(width: ScreenWidth) {
  const theme = useTheme()
  const viewportWidth = useViewportWidth()
  const valueString = th.screen(width)({ theme }) as string
  const value = Number(valueString.replace('px', ''))

  return { value, viewportWidth }
}

/** Returns true if current viewport width is smaller than provided width. */
export function useDown(width: ScreenWidth) {
  const { value, viewportWidth } = useMedia(width)

  return viewportWidth !== null && value !== null && viewportWidth < value
}

/** Returns true if current viewport width is larger than or equal to provided width. */
export function useUp(width: ScreenWidth) {
  const { value, viewportWidth } = useMedia(width)

  return viewportWidth !== null && value !== null && viewportWidth >= value
}

/** Returns true if current viewport width is between provided min and max widths. */
export function useBetween(minWidth: ScreenWidth, maxWidth: ScreenWidth) {
  const isDown = useDown(maxWidth)
  const isUp = useUp(minWidth)

  return isDown && isUp
}

/** Return the width of the viewport. */
export { useViewportWidth }
