import React, { useCallback, useEffect, useState, useRef } from 'react'
import PropTypes from 'prop-types'
import { GU, useViewport, unselectable, springs } from '@aragon/ui'
import { animated, useSpring } from 'react-spring/hooks'
import { useDrag } from 'react-use-gesture'
import PrevNext from './PrevNext'

// TODO:
//  - Center the items when the total is smaller than the viewport.

const AnimatedDiv = animated.div

function Carousel({ items, itemWidth, itemHeight, itemSpacing }) {
  const [selected, setSelected] = useState(0)
  const [containerWidth, setContainerWidth] = useState(0)
  const [visibleItems, setVisibleItems] = useState(0)
  const container = useRef(null)
  const { width: vw } = useViewport()

  // Set the number of visible items,
  // and adjust the selected item if needed.
  useEffect(() => {
    const visibleItems = Math.max(
      1,
      Math.floor((containerWidth - itemSpacing * 2) / (itemWidth + itemSpacing))
    )
    const lastSelectionableItem = items.length - visibleItems

    setVisibleItems(visibleItems)
    setSelected(selected =>
      selected > lastSelectionableItem ? lastSelectionableItem : selected
    )
  }, [containerWidth, itemSpacing, itemWidth, items])

  const updateContainerWidth = useCallback(element => {
    setContainerWidth(element ? element.clientWidth : 0)
  }, [])

  useEffect(() => {
    if (container.current) {
      updateContainerWidth(container.current)
    }
  }, [vw, updateContainerWidth])

  const handleContainerRef = useCallback(
    element => {
      container.current = element
      updateContainerWidth(element)
    },
    [updateContainerWidth]
  )

  const prev = useCallback(() => {
    setSelected(selected => Math.max(0, selected - visibleItems))
  }, [visibleItems])

  const next = useCallback(() => {
    setSelected(selected =>
      Math.min(items.length - visibleItems, selected + visibleItems)
    )
  }, [items.length, visibleItems])

  // The total width of the visible items
  const visibleItemsWidth =
    visibleItems * itemWidth + (visibleItems - 1) * itemSpacing

  // The space on one side of the visible items
  const sideSpace = (containerWidth - visibleItemsWidth) / 2

  // Get the container x position from an item index
  const xFromItem = useCallback(
    index => sideSpace - (itemWidth + itemSpacing) * index,
    [sideSpace, itemWidth, itemSpacing]
  )

  // Get an item index from the container x position
  /*
  const itemFromX = useCallback(
    x =>
      Math.max(
        0,
        Math.min(
          items.length - visibleItems,

          // We multiply by -1 because the resulting number is either negative
          // (if the first item starts to disappear on the left edge), or 0.
          Math.floor(x / (itemWidth + itemSpacing)) * -1
        )
      ),
    [items, itemWidth, itemSpacing, visibleItems]
  )
  */

  // The current x position, before the drag
  const selectedX = xFromItem(selected)

  // The x position of the last item, before the drag
  const lastX = xFromItem(items.length - visibleItems)

  // Handles the actual x position, with the drag
  const [{ x }, setX] = useSpring(() => ({
    x: selectedX,
    config: springs.lazy,
    drag: false,
    immediate: true,
  }))

  // Update the transition during drag
  const bindDrag = useDrag(({ event, down, delta }) => {
    const updatedX = Math.max(lastX, Math.min(sideSpace, selectedX + delta[0]))

    if (down) {
      setX({
        x: updatedX,
        drag: delta[0] !== 0,
        immediate: true, // TODO: keep going until we decelerate enough, then stick to an item
      })
    } else {
      let target = selected
      if (Math.abs(delta[0]) > itemWidth / 2) {
        if (delta[0] > 0) {
          target = Math.max(0, selected - 1)
        } else {
          target = Math.min(selected + 1, items.length - 1)
        }
      }

      setX({
        x: xFromItem(target),
        drag: false,
        immediate: false,
      })
      setSelected(target)
    }
  })

  // Update the transition when the base x position updates
  useEffect(() => {
    setX({ x: selectedX, immediate: false })
  }, [selectedX, setX])

  return (
    <div
      ref={handleContainerRef}
      css={`
        ${unselectable};
        position: relative;
        overflow: hidden;
        width: 100%;
        height: ${itemHeight}px;
        touch-action: none;
      `}
    >
      {selected > 0 && <PrevNext type="previous" onClick={prev} />}
      {selected < items.length - visibleItems && (
        <PrevNext type="next" onClick={next} />
      )}
      <AnimatedDiv
        {...bindDrag()}
        style={{
          transform: x.interpolate(x => `translate3d(${(0, x)}px, 0, 0)`),
          // transform: selectedX.interpolate(v => `translate3d(${v}px, 0, 0)`),
        }}
        css={`
          display: flex;
          height: 100%;
          position: absolute;
          touch-action: none;
        `}
      >
        {items.map((item, i) => (
          <AnimatedDiv
            key={i}
            // style={{
            //   opacity: drag.interpolate(drag =>
            //     drag || (i >= selected && i < selected + visibleItems)
            //       ? 1
            //       : 0.25
            //   ),
            // }}
            css={`
              flex-grow: 0;
              flex-shrink: 0;
              width: ${itemWidth}px;
              height: ${itemHeight}px;
              transition: opacity 150ms ease-in-out;
              & + & {
                margin-left: ${3 * GU}px;
              }
            `}
          >
            {item}
          </AnimatedDiv>
        ))}
      </AnimatedDiv>
    </div>
  )
}

Carousel.propTypes = {
  items: PropTypes.arrayOf(PropTypes.node).isRequired,
  itemWidth: PropTypes.number.isRequired,
  itemHeight: PropTypes.number.isRequired,
  itemSpacing: PropTypes.number.isRequired,
}

export default Carousel
