/*
 * Copyright (c) 2018-present, Revolut LTD.
 *
 * This source code is licensed under the Apache 2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */
import * as React from 'react'
import { useEffect, useRef, RefObject } from 'react'
import { Portal } from 'react-portal'
import { TransitionStatus } from 'react-transition-group/Transition'
import { usePrevious } from 'react-hanger'
import styled from 'styled-components'
import { zIndex as ssZIndex } from 'styled-system'

import { propGet } from '../../style/utils'
import { Animation, getDuration, isEntered, isEntering, TimeoutType } from '../Animation'

const SIDE = {
  LEFT: 'left',
  RIGHT: 'right',
}
const MODE = {
  OVERLAY: 'overlay',
  SQUEEZE: 'squeeze',
}
const OFFSET_TO_HIDE_SHADOW = '3vw'

const isLeft = (side: string) => side === SIDE.LEFT
const isRight = (side: string) => side === SIDE.RIGHT
const isOverlay = (mode: string) => mode === MODE.OVERLAY
const isSqueeze = (mode: string) => mode === MODE.SQUEEZE

const positioningAndShadow = ({ state, side, mode }: MenuContainerProps) => {
  const boxShadow = '0 1px 26px 3px #000'
  const shouldApplyEnterStyles = isEntering(state) || isEntered(state)
  const isOverlayMode = isOverlay(mode)
  if (shouldApplyEnterStyles && isRight(side)) {
    return { right: 0, boxShadow: isOverlayMode ? boxShadow : '' }
  }
  if (shouldApplyEnterStyles && isLeft(side)) {
    return { left: 0, boxShadow: isOverlayMode ? boxShadow : '' }
  }
  return { boxShadow: isOverlayMode ? boxShadow : '' }
}

type MenuContainerProps = SideMenuProps & { state: TransitionStatus }

const StyledMenuContainer = styled.div<MenuContainerProps>`
  position: fixed;
  top: ${propGet('topOffset')};
  min-height: calc(100vh - ${propGet('topOffset')} - ${propGet('bottomOffset')});
  max-height: 100%;
  background: white;
  padding: 1rem;
  transition: ${propGet('side')} ${propGet('duration')}ms;
  width: ${propGet('width')};
  max-width: ${propGet('width')};
  border: 1px solid lightgray;
  ${propGet('side')} calc(-${propGet('width')} - ${OFFSET_TO_HIDE_SHADOW});
  // @ts-ignore
  ${positioningAndShadow};
  ${ssZIndex};
`

const useHandleElementSqueeze = ({
  isOpen,
  mode,
  side,
  width,
  duration,
  elementToSqueeze,
}: SideMenuProps) => {
  const element = useRef(null)
  const isOpenPrev = usePrevious(isOpen)
  useEffect(
    () => {
      if (isSqueeze(mode)) {
        element.current = document.querySelector<HTMLElement>(elementToSqueeze)
        if (element.current) {
          if (isOpen === true) {
            if (isLeft(side)) element.current.style.paddingLeft = width
            else element.current.style.paddingRight = width
          }
          element.current.style.transition = `padding ${getDuration(duration).enter}ms`
        }
      }
      return () => {
        if (element.current) {
          if (isLeft(side)) {
            element.current.style.paddingLeft = '0'
          } else {
            element.current.style.paddingRight = '0'
          }
        }
      }
    },
    [duration, elementToSqueeze, isOpen, mode, side, width],
  )

  useEffect(
    () => {
      const isClosing = isOpenPrev === true
      if (isOpenPrev !== isOpen && isSqueeze(mode) && element.current) {
        if (isClosing) {
          if (isLeft(side)) {
            element.current.style.paddingLeft = '0'
          } else {
            element.current.style.paddingRight = '0'
          }
        } else if (isLeft(side)) {
          element.current.style.paddingLeft = width
        } else {
          element.current.style.paddingRight = width
        }
      }
    },
    [isOpen, isOpenPrev, mode, side, width],
  )
}

type SideMenuRef<T> = ((instance: T) => void) | RefObject<T>

export type SideMenuProps = {
  width?: string
  topOffset?: string
  bottomOffset?: string
  isOpen: boolean
  side?: 'right' | 'left'
  elementToSqueeze?: string
  mode?: 'squeeze' | 'overlay'
  duration?: TimeoutType | number
  zIndex?: number
  sideMenuRef?: SideMenuRef<HTMLDivElement>
}

export const SideMenu: React.FC<SideMenuProps> = ({
  width = '300px',
  topOffset = '0px',
  bottomOffset = '0px',
  isOpen,
  side = 'right',
  elementToSqueeze = 'body',
  mode = 'overlay',
  duration = 500,
  zIndex = 10,
  sideMenuRef,
  ...rest
}) => {
  // sideMenuRef should be changed to RefForwarding component wrapper
  const propsWithDefaults = {
    width,
    topOffset,
    bottomOffset,
    isOpen,
    side,
    elementToSqueeze,
    mode,
    duration,
    zIndex,
    sideMenuRef,
    ...rest,
  }
  useHandleElementSqueeze(propsWithDefaults)
  return (
    <Animation in={isOpen} timeout={duration}>
      {({ duration: animationDuration, state }) => (
        <Portal>
          <StyledMenuContainer
            {...propsWithDefaults}
            ref={sideMenuRef}
            state={state}
            duration={animationDuration}
          >
            {propsWithDefaults.children}
          </StyledMenuContainer>
        </Portal>
      )}
    </Animation>
  )
}
