import * as React from 'react'
import * as PropTypes from 'prop-types'
import * as ReachSlider from '@reach/slider'
import styled from 'styled-components'
import stringify from '../../utilities/stringify'
import useHydration from '../../utilities/hooks/useHydration'

const Wrapper = styled('div')({
  position: 'relative',
  display: 'inline-block',
  verticalAlign: 'top',
  ':focus-within': {
    '--color-accent': 'var(--color-active, Highlight)',
  },
})

Wrapper.displayName = 'div'

const Base = styled(ReachSlider.SliderInput)({
  boxSizing: 'border-box',
  position: 'absolute',
  bottom: 0,
  right: 0,
  ':active': {
    cursor: 'grabbing',
  },
})

Base.displayName = 'input'

const Track = styled(ReachSlider.SliderTrack)({
  borderRadius: '0.125rem',
  position: 'relative',
  width: '100%',
  height: '100%',
  '::before': {
    display: 'block',
    position: 'absolute',
    content: "''",
    opacity: 0.25,
    width: '100%',
    height: '100%',
    backgroundColor: 'currentColor',
    borderRadius: '0.125rem',
  },
})

const Handle = styled(ReachSlider.SliderHandle)({
  cursor: 'grab',
  width: '1.25rem',
  height: '1.25rem',
  backgroundColor: 'var(--color-accent, blue)',
  borderRadius: '50%',
  zIndex: 1,
  transformOrigin: 'center',
  outline: 0,
  position: 'relative',
  '::before': {
    position: 'absolute',
    top: 0,
    left: 0,
    width: '100%',
    height: '100%',
    content: "''",
    borderRadius: '50%',
    opacity: 0.5,
    pointerEvents: 'none',
  },
  ':focus::before': {
    boxShadow: '0 0 0 0.25rem var(--color-accent, blue)',
  },
})

const Highlight = styled(ReachSlider.SliderTrackHighlight)({
  backgroundColor: 'var(--color-accent, blue)',
  borderRadius: '0.125rem',
})

const SizingWrapper = styled('span')({
  width: '100%',
  display: 'inline-block',
  verticalAlign: 'top',
  position: 'relative',
})

SizingWrapper.displayName = 'SizingWrapper'

const TransformWrapper = styled('span')({
  display: 'inline-block',
  verticalAlign: 'top',
  transformOrigin: 'top left',
})

TransformWrapper.displayName = 'TransformWrapper'

const LabelWrapper = styled('span')({
  color: 'var(--color-accent, blue)',
  boxSizing: 'border-box',
  fontSize: '0.75em',
  maxWidth: '100%',
  overflow: 'hidden',
  textOverflow: 'ellipsis',
  whiteSpace: 'nowrap',
  fontWeight: 'bolder',
  zIndex: 2,
  pointerEvents: 'none',
  transformOrigin: 'top left',
  position: 'absolute',
  top: 0,
  left: 0,
  transitionProperty: 'color',
})

LabelWrapper.displayName = 'span'

const FallbackTrack = styled('span')({
  padding: '1.875rem 0.75rem 0.875rem',
  boxSizing: 'border-box',
  position: 'absolute',
  top: 0,
  left: 0,
  pointerEvents: 'none',
  '::before': {
    display: 'block',
    content: "''",
    opacity: 0.25,
    width: '100%',
    height: '100%',
    backgroundColor: 'currentColor',
    borderRadius: '0.125rem',
  },
})

const FallbackSlider = styled('input')({
  boxSizing: 'border-box',
  backgroundColor: 'transparent',
  verticalAlign: 'top',
  margin: 0,
  width: '100%',
  height: '2rem',
  marginTop: '1rem',
  appearance: 'none',
  outline: 0,
  position: 'absolute',
  left: 0,
  '::-moz-focus-inner': {
    outline: 0,
    border: 0,
  },
  '::-webkit-slider-thumb': {
    cursor: 'grab',
    width: '1.25rem',
    height: '1.25rem',
    backgroundColor: 'var(--color-accent, blue)',
    borderRadius: '50%',
    zIndex: 1,
    transformOrigin: 'center',
    outline: 0,
    position: 'relative',
    appearance: 'none',
  },
  '::-moz-range-thumb': {
    cursor: 'grab',
    border: 0,
    width: '1.25rem',
    height: '1.25rem',
    backgroundColor: 'var(--color-accent, blue)',
    borderRadius: '50%',
    zIndex: 1,
    transformOrigin: 'center',
    outline: 0,
    position: 'relative',
    appearance: 'none',
  },
})

FallbackSlider.displayName = 'input'

const ClickArea = styled('label')({
  verticalAlign: 'top',
  width: '100%',
  height: '100%',
})

ClickArea.displayName = 'label'

export type Orientation = 'vertical' | 'horizontal'

type Dimension = 'width' | 'height'

const propTypes = {
  /**
   * The component orientation.
   */
  orientation: PropTypes.oneOf<Orientation>(['vertical', 'horizontal']),
  /**
   * Short textual description indicating the nature of the component's value.
   */
  label: PropTypes.any,
  /**
   * CSS size for the component length.
   */
  length: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  /**
   * Is the component active?
   */
  disabled: PropTypes.bool,
  /**
   * Event handler triggered when the component changes value.
   */
  onChange: PropTypes.func,
  /**
   * Use the sSR-friendly rendering of the component.
   */
  fallback: PropTypes.bool,
  /**
   * Default value of the component.
   */
  defaultValue: PropTypes.any,
  /**
   * Value of the component.
   */
  value: PropTypes.any,
}

type Props = PropTypes.InferProps<typeof propTypes>

/**
 * Component for inputting numeric values in a graphical manner.
 *
 * The component is styled using client-side scripts. When the component is rendered server-side,
 * the component falls back into the original `<input type="range">` element.
 *
 * @see {@link //reacttraining.com/reach-ui/slider/#sliderinput|Reach UI Slider} for the client-side implementation.
 * @param {'vertical' | 'horizontal'} [orientation] - The component orientation.
 * @param {*} [label] - Short textual description indicating the nature of the component's value.
 * @param {string | number} [length] - CSS size for the component length.
 * @param {boolean} [disabled] - Is the component active?
 * @param [onChange] - Event handler triggered when the component changes value.
 */
const Slider = React.forwardRef<unknown, Props>(({
  orientation = 'horizontal',
  label = '',
  length = '16rem',
  disabled = false,
  onChange,
  fallback = false,
}, ref) => {
  const isClient = useHydration()

  React.useEffect(() => {
    window.document.body.style.setProperty('--reach-slider', '1')
  }, [])

  const parallelDimension: Dimension = orientation === 'horizontal' ? 'width' : 'height'

  const perpendicularDimension: Dimension = orientation === 'horizontal' ? 'height' : 'width'

  const perpendicularReference = orientation === 'horizontal' ? 'top' : 'left'

  const perpendicularTransform = orientation === 'horizontal' ? 'translateY' : 'translateX'

  const customBaseProps: any = {
    orientation,
  }

  const customFallbackProps: any = {
    orient: orientation,
  }

  if (!fallback && isClient) {
    return (
      <Wrapper
        style={{
          opacity: disabled ? 0.5 : undefined,
          [parallelDimension]: length!,
        }}
      >
        <ClickArea>
          <SizingWrapper
            style={{
              [parallelDimension]: length!,
              [perpendicularDimension]: '3rem',
            }}
          >
            <TransformWrapper
              style={{
                [parallelDimension]: length!,
                [perpendicularDimension]: '100%',
                transform:
                  orientation === 'horizontal'
                    ? undefined
                    : `rotate(-90deg) translateX(calc(${
                        typeof length! === 'number' ? `${length as number}px` : (length as string)
                      } * -1))`,
              }}
            >
              <LabelWrapper
                style={{
                  maxWidth: length!,
                }}
              >
                {stringify(label)}
              </LabelWrapper>
            </TransformWrapper>
          </SizingWrapper>
          <Base
            {...customBaseProps}
            disabled={disabled!}
            style={{
              [parallelDimension]: length,
              [perpendicularDimension]: '2rem',
              padding: orientation === 'horizontal' ? '0.875rem 0.75rem' : '0.75rem 0.875rem',
              cursor: disabled ? 'not-allowed' : undefined,
            }}
            onChange={onChange!}
            ref={ref}
          >
            <Track>
              <Highlight
                style={{
                  [perpendicularDimension]: '100%',
                }}
              />
              <Handle
                style={{
                  [perpendicularReference]: '50%',
                  transform: `${perpendicularTransform}(-50%)`,
                  cursor: disabled ? 'not-allowed' : undefined,
                }}
              />
            </Track>
          </Base>
        </ClickArea>
      </Wrapper>
    )
  }

  return (
    <Wrapper
      style={{
        opacity: disabled ? 0.5 : undefined,
        [parallelDimension]: length!,
      }}
    >
      <SizingWrapper
        style={{
          [parallelDimension]: length!,
          [perpendicularDimension]: '3rem',
        }}
      >
        <TransformWrapper
          style={{
            [parallelDimension]: length!,
            [perpendicularDimension]: '100%',
            transform:
              orientation === 'horizontal'
                ? undefined
                : `rotate(-90deg) translateX(calc(${typeof length! === 'number' ? `${length}px` : length} * -1))`,
          }}
        >
          <FallbackTrack
            style={{
              width: length!,
              height: '3rem',
              padding: '1.875rem 0.75rem 0.875rem',
            }}
          />
          <ClickArea>
            <LabelWrapper
              style={{
                maxWidth: length!,
              }}
            >
              {stringify(label)}
            </LabelWrapper>
            {stringify(label).length > 0 && ' '}
            <FallbackSlider
              {...customFallbackProps}
              style={{
                width: length!,
              }}
              disabled={disabled!}
              onChange={onChange!}
              type="range"
              ref={ref}
            />
          </ClickArea>
        </TransformWrapper>
      </SizingWrapper>
    </Wrapper>
  )
})

Slider.propTypes = propTypes

Slider.displayName = 'Slider'

export default Slider
