import React, { useRef, useState, useEffect, useCallback } from 'react'
import { AnimationController } from './animation'
import Item from './GridItem'

interface Animation {
  mode?: 'fade' | 'slideLeft' | 'slideRight'
  duration?: number
  speed?: number
  reverse?: boolean
}

type AlignMode = 'start' | 'center' | 'end'

interface Layout {
  rows: number
  rowsGap?: number
  columns: number
  columnsGap?: number
  alignment?: {
    h: AlignMode
    v: AlignMode
  }
}

interface Border {
  style: 'none' | 'horizontal' | 'vertical' | 'both'
  width: number
  color: string
}

type GridProps = {
  animate: boolean,
  animation: Animation,
  border?: Border,
  fontSize?: string
  id: string
  items: React.ReactNode[]
  layout: Layout,
  style?: React.CSSProperties
}

type GridState = {
  show: boolean,
  stage: 'idle' | 'preparing' | 'prepared' | 'animating',
  items: React.ReactNode[]
}

export const Grid: React.FunctionComponent<GridProps> = (props) => {
  const { animate, animation, border, fontSize, id, items, layout, style } = props
  const containerRef = useRef<HTMLDivElement>(null)
  const lastIndexRef = useRef<number>(0)
  const animationRef = useRef<any>(new AnimationController())
  const animatingRef = useRef<boolean>(false)
  const unmountingRef = useRef<boolean>(false)

  const columnsRef = useRef<number>(1)
  const columnsGapRef = useRef<number>(0)
  const rowsRef = useRef<number>(1)
  const rowsGapRef = useRef<number>(0)
  const itemsCountRef = useRef<number>(0)

  const [state, setState] = useState<GridState>({
    show: false,
    stage: 'idle',
    items: []
  })

  useEffect(() => {
    return () => {
      unmountingRef.current = true
    }
  }, [])

  const configure = useCallback(() => {
    animationRef.current.configure(animation)
    columnsRef.current = layout.columns ?? 1
    columnsGapRef.current = layout.columnsGap ?? 0
    rowsRef.current = layout.rows ?? 1
    rowsGapRef.current = layout.rowsGap ?? 0
    itemsCountRef.current = rowsRef.current * columnsRef.current
    if (animate && !animatingRef.current && state.stage === 'idle') {
      animatingRef.current = true
      createItems()
    }
  }, [animation, layout, animate, state.stage])

  const createItems = useCallback(() => {
    if (!items) return
    let count = 0
    lastIndexRef.current = lastIndexRef.current < items.length ? lastIndexRef.current : 0

    let filteredItems = items.filter((_item, index) => {
      return index >= lastIndexRef.current
    })

    if (animation.reverse) filteredItems = [...filteredItems].reverse()

    let newItems = filteredItems.map((item: React.ReactNode, index, arr) => {
      if (count >= itemsCountRef.current) return null
      lastIndexRef.current++
      count++
      return <Item
        key={`item-${id}-${index}-${lastIndexRef.current}`} containerData={{
          ic_index: index,
          ic_rowIndex: Math.floor(index / columnsRef.current),
          ic_rows: rowsRef.current,
          ic_columnIndex: Math.floor(index % columnsRef.current),
          ic_columns: columnsRef.current,
          ic_firstChild: index === 0,
          ic_lastChild: index === (arr.length - 1)
        }} style={animationRef.current.getInitialStyle()}
        // Item alignment. By default is center - center.
        alignment={layout.alignment}
        // Setup item width based on columns count.
        width={100 / columnsRef.current}
        // Setup item height based on rows count. If 0 set up on auto.
        height={100 / rowsRef.current}
        horizontalGap={columnsGapRef.current}
        verticalGap={rowsGapRef.current}
        border={border}
        fontSize={fontSize}>
        {item}
      </Item>
    })
    if (newItems.length > 0) {
      setState({
        show: true,
        stage: 'prepared',
        items: newItems
      })
    } else {
      setState({
        show: false,
        stage: 'idle',
        items: []
      })
      animatingRef.current = false
    }
  }, [items, id, layout.alignment, border, fontSize, animation.reverse])

  useEffect(() => {
    configure()
  }, [props, configure])

  useEffect(() => {
    if (animate && state.stage === 'prepared') {
      animationRef.current.animate(containerRef.current!.childNodes, () => {
        !unmountingRef.current && createItems()
      })
      setState(prevState => ({
        ...prevState,
        stage: 'animating'
      }))
    }
  }, [props, state.stage, createItems])

  return (
    <div ref={containerRef} style={{
      opacity: state.show ? 1 : 0,
      position: 'relative',
      display: 'flex',
      flexWrap: 'wrap',
      alignContent: 'flex-start',
      ...style
    }}>
      {state.items}
    </div>
  )
}
