import React, { Children, createRef, forwardRef } from 'react';
import PropTypes from 'prop-types';
import styled, { keyframes } from 'styled-components';
import { Flipped } from 'react-flip-toolkit';

const getFadeContainerKeyFrame = ({ animatingOut, direction }) => {
  if (!direction) {
    return;
  }
  return keyframes`
      to {
        transform: translateX(0px);
        opacity: ${animatingOut ? 0 : 1};
      }
    `;
};

const FadeContainer = styled.div`
  will-change: transform;
  animation-name: ${getFadeContainerKeyFrame};
  animation-duration: ${props => props.duration}ms;
  animation-fill-mode: forwards;
  opacity: ${props => (props.direction && !props.animatingOut ? 0 : 1)};
  top: 0;
  left: 0;
`;

const FadeContents = forwardRef(({ children, duration, animatingOut, direction }, ref) => (
  <FadeContainer
    // prevent screen readers from reading out hidden content
    aria-hidden={animatingOut}
    animatingOut={animatingOut}
    direction={direction}
    duration={duration}
    ref={ref}
  >
    {children}
  </FadeContainer>
));

FadeContents.displayName = 'FadeContents';
FadeContents.propTypes = {
  duration: PropTypes.number,
  direction: PropTypes.oneOf(['right', 'left']),
  animatingOut: PropTypes.bool,
  children: PropTypes.node
};

const getDropdownRootKeyFrame = ({ animatingOut, direction }) => {
  if (!animatingOut && direction) {
    return null;
  }
  return keyframes`
      from {
        transform: ${animatingOut ? 'rotateX(0)' : 'rotateX(-15deg)'};
        opacity: ${animatingOut ? 1 : 0};
      }
      to {
        transform: ${animatingOut ? 'rotateX(-15deg)' : 'rotateX(0)'};
        opacity: ${animatingOut ? 0 : 1};
      }
    `;
};

const DropdownRoot = styled.div`
  transform-origin: 0 0;
  will-change: transform;
  animation-name: ${getDropdownRootKeyFrame};
  animation-duration: ${props => props.duration}ms;
  /* use 'forwards' to prevent flicker on leave animation */
  animation-fill-mode: forwards;
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  top: 10px;
`;

const DropdownBackground = styled.div`
  transform-origin: 0 0;
  background-color: white;
  border-radius: 12px;
  overflow: hidden;
  position: relative;
  box-shadow:
    0 5.5px 2.2px rgba(0, 0, 0, 0.028),
    0 18.3px 13.9px rgba(0, 0, 0, 0.042),
    0 82px 80px rgba(0, 0, 0, 0.07);
  will-change: transform;
  border-radius: 16px;
`;

const AltBackground = styled.div`
  background-color: white;
  width: 300%;
  height: 100%;
  position: absolute;
  top: 0;
  left: -100%;
  transform-origin: 0 0;
  z-index: 0;
  transition: transform ${props => props.duration}ms;
`;

const InvertedDiv = styled.div`
  will-change: transform;
  position: ${props => (props.absolute ? 'absolute' : 'relative')};
  top: 0;
  left: 0;
  &:first-of-type {
    z-index: 1;
  }
  &:not(:first-of-type) {
    z-index: -1;
  }
`;

const getFirstDropdownSectionHeight = el => {
  if (!el || !el.querySelector || !el.querySelector('*[data-first-dropdown-section]')) return 0;
  return el.querySelector('*[data-first-dropdown-section]').offsetHeight;
};

const updateAltBackground = ({ altBackground, prevDropdown, currentDropdown }) => {
  const prevHeight = getFirstDropdownSectionHeight(prevDropdown);
  const currentHeight = getFirstDropdownSectionHeight(currentDropdown);

  const immediateSetTranslateY = (el, translateY) => {
    el.style.transform = `translateY(${translateY}px)`;
    el.style.transition = 'transform 0s';
    requestAnimationFrame(() => (el.style.transitionDuration = ''));
  };

  if (prevHeight) {
    // transition the grey ("alt") background from its previous height to its current height
    immediateSetTranslateY(altBackground, prevHeight);
    requestAnimationFrame(() => {
      altBackground.style.transform = `translateY(${currentHeight}px)`;
    });
  } else {
    // just immediately set the background to the appropriate height
    // since we don't have a stored value
    immediateSetTranslateY(altBackground, currentHeight);
  }
};

const DropdownContainer = ({ children, direction, animatingOut, duration }) => {
  let altBackgroundEl;

  const currentDropdownEl = createRef();
  const prevDropdownEl = createRef();

  React.useEffect(() => {
    updateAltBackground({
      altBackground: altBackgroundEl,
      prevDropdown: prevDropdownEl.current,
      currentDropdown: currentDropdownEl.current
    });
  }, []);

  const [currentDropdown, prevDropdown] = Children.toArray(children);

  return (
    <DropdownRoot direction={direction} animatingOut={animatingOut} duration={duration}>
      <Flipped flipId="dropdown">
        <DropdownBackground>
          <Flipped inverseFlipId="dropdown">
            <InvertedDiv>
              <AltBackground ref={el => (altBackgroundEl = el)} duration={duration} />
              <FadeContents direction={direction} duration={duration} ref={currentDropdownEl}>
                {currentDropdown}
              </FadeContents>
            </InvertedDiv>
          </Flipped>

          <Flipped inverseFlipId="dropdown" scale>
            <InvertedDiv absolute>
              {prevDropdown && (
                <FadeContents animatingOut direction={direction} duration={duration} ref={prevDropdownEl}>
                  {prevDropdown}
                </FadeContents>
              )}
            </InvertedDiv>
          </Flipped>
        </DropdownBackground>
      </Flipped>
    </DropdownRoot>
  );
};

DropdownContainer.propTypes = {
  duration: PropTypes.number,
  direction: PropTypes.oneOf(['right', 'left']),
  animatingOut: PropTypes.bool,
  children: PropTypes.node
};

export default DropdownContainer;
