import React, { Children, useContext, useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import animations from '../animations'
import styled, { ThemeContext } from 'styled-components'
import useEventListener from '../hooks/use-event-listener'
import { getViewportSize } from '../utils'

const Container = styled.div`
  position: relative;
`

const imgWrapper = styled.div`
  & > img {
    width: 100%;
    height: auto;
    display: block;
  }
`

const Original = styled(imgWrapper)`
  visibility: hidden;
  position: relative;
`

const Overlay = styled.div`
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  opacity: 0;
  transition: opacity ${props => props.transitionDuration}ms;
  background: ${props => props.background};
`

const Zoomable = styled(imgWrapper)`
  cursor: pointer;
  cursor: zoom-in;
  position: absolute;
  left: 0;
  top: 0;
`

const Caption = styled.figcaption`
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  visibility: hidden;
  margin: 0;
  color: ${props => props.color};
  font-size: ${props => props.fontSize}px;
  line-height: ${props => props.lineHeight}px;
  letter-spacing: ${props => props.letterSpacing}px;
  text-align: left;
  ${props => (props.fontFamily ? `font-family: ${props.fontFamily};` : '')}
`

const Zoom = props => {
  const themeContext = useContext(ThemeContext)
  const { zoomOptions } = themeContext
  const animate = animations[themeContext.caption.side] || animations.default
  const overlayRef = useRef(null)
  const originalRef = useRef(null)
  const zoomedRef = useRef(null)
  const containerRef = useRef(null)
  const captionRef = useRef(null)

  let isAnimating = false
  const setAnimating = bool => {
    isAnimating = bool
  }

  let isZoomed = false
  const setZoom = bool => {
    isZoomed = bool
  }

  const getCaptionHeight = captionLength => {
    const { fontSize, letterSpacing, width, lineHeight } = themeContext.caption
    const height =
      Math.ceil((captionLength * (fontSize + letterSpacing)) / width) *
      lineHeight
    return height
  }

  const handleOpenEnd = () => {
    if (!isAnimating) return
    if (!captionRef.current) return

    setAnimating(false)
    captionRef.current.style.opacity = 1
  }

  const handleCloseEnd = () => {
    if (!containerRef.current || !overlayRef.current || !zoomedRef.current)
      return

    overlayRef.current.style.cursor = ''
    zoomedRef.current.setAttribute(
      'style',
      `position: absolute;
		   left: 0;
       top: 0;
       cursor: pointer;
       cursor: zoom-in;`
    )
    setAnimating(false)
    containerRef.current.style.zIndex = '0'
    overlayRef.current.style.display = 'none'
  }

  const handleTransitionEnd = () =>
    isZoomed ? handleOpenEnd() : handleCloseEnd()

  let scrollTop = 0

  const open = ({ target } = {}) => {
    if (!target) return
    if (isZoomed) return
    if (!containerRef.current || !overlayRef.current || !zoomedRef.current)
      return

    const { overlay } = themeContext
    const { width: clientWidth, height: clientHeight } = getViewportSize()

    scrollTop =
      window.pageYOffset ||
      document.documentElement.scrollTop ||
      document.body.scrollTop ||
      0

    setAnimating(true)
    setZoom(true)

    containerRef.current.style.zIndex = overlay.zIndex
    overlayRef.current.style.display = 'block'

    overlayRef.current.setAttribute(
      'style',
      `cursor: pointer;
      cursor: zoom-out;`
    )
    zoomedRef.current.setAttribute(
      'style',
      `cursor: pointer;
      cursor: zoom-out;`
    )

    animate({
      originalNode: originalRef.current,
      zoomedNode: zoomedRef.current,
      captionNode: captionRef.current,
      themeContext,
      captionHeight: getCaptionHeight(props.caption.length),
      clientWidth,
      clientHeight,
    })

    overlayRef.current.style.opacity = overlay.opacity
  }

  const close = () => {
    if (isAnimating || !isZoomed) return
    if (
      !overlayRef.current ||
      !zoomedRef.current ||
      !captionRef.current ||
      !originalRef.current
    )
      return

    setAnimating(true)
    setZoom(false)

    overlayRef.current.style.opacity = '0'
    zoomedRef.current.style.transform = ''

    captionRef.current.setAttribute(
      'style',
      `position: absolute;
       top: 0;
       left: 0;
       opacity: 0;
       visibility: hidden;`
    )
    originalRef.current.setAttribute(
      'style',
      `visibility: hidden;
       position: relative;`
    )
  }

  const toggle = ({ target } = {}) => {
    if (isZoomed) {
      return close()
    }
    if (target) {
      return open({ target })
    }
  }

  const handleScroll = () => {
    if (isAnimating || !isZoomed) return

    const currentScroll =
      window.pageYOffset ||
      document.documentElement.scrollTop ||
      document.body.scrollTop ||
      0

    if (Math.abs(scrollTop - currentScroll) > zoomOptions.scrollOffset) {
      setTimeout(close, 50)
    }
  }

  const handleKeyUp = event => {
    const key = event.key || event.keyCode

    // Close if escape key is pressed
    if (key === 'Escape' || key === 'Esc' || key === 27) {
      close()
    }
  }

  useEffect(() => {
    if (!originalRef.current || !zoomedRef.current) return
    originalRef.current.style.width = '100%'
    zoomedRef.current.style.width = '100%'
    overlayRef.current.style.display = 'none'
  }, [])
  useEventListener(document, 'keyup', handleKeyUp)
  useEventListener(document, 'scroll', handleScroll)
  useEventListener(window, 'resize', close)
  useEventListener(zoomedRef, 'transitionend', handleTransitionEnd)

  if (Children.count(props.children) !== 1) {
    console.error(`
      Error: Adjacent JSX elements must be wrapped in an enclosing tag. 
      Did you want a <div>...</div>?
    `)
    return null
  }

  const { overlay, caption } = themeContext

  return (
    <Container ref={containerRef}>
      <Original ref={originalRef} onClick={toggle}>
        {props.children}
      </Original>
      <Overlay
        ref={overlayRef}
        background={overlay.background}
        onClick={close}
        transitionDuration={zoomOptions.transitionDuration}
      />
      <Zoomable ref={zoomedRef} onClick={toggle}>
        {props.children}
      </Zoomable>
      <Caption
        ref={captionRef}
        fontSize={caption.fontSize}
        lineHeight={caption.lineHeight}
        letterSpacing={caption.letterSpacing}
        color={caption.color}
        fontFamily={caption.fontFamily}
      >
        {props.caption}
      </Caption>
    </Container>
  )
}

Zoom.propTypes = {
  children: PropTypes.node.isRequired,
  caption: PropTypes.string,
}

Zoom.defaultProps = {
  caption: '',
}

export default Zoom