import { Dispatch, ReactNode, SetStateAction, createContext, useCallback, useContext, useEffect, useState } from 'react'
import styled from 'styled-components'
import { InferComponentProps } from './types'

const ColumnCarouselContainer = styled.div`
  display: flex;
  flex-direction: row;
  & > * {
    flex: 1;
  }
`

type InferContextType<T> = T extends React.Context<infer U> ? U : never

const ColumnCarouselContext = createContext({
  /** @private */
  currentColumnIndex: 0,
  /** @private */
  setCurrentColumnIndex: (() => {}) as Dispatch<SetStateAction<number>>,
  cycle: () => {},
  goForward: () => {},
  goBackward: () => {},
  goToIndex: (_index: number) => {},
  activeColumns: 0,
  totalColumns: 0,
  canCycle: false,
})

type ColumnCarouselProviderProps = {
  children: ReactNode
  activeColumns?: number
  totalColumns: number
}

export const ColumnCarouselProvider = ({
  totalColumns,
  activeColumns = totalColumns,
  children,
}: ColumnCarouselProviderProps) => {
  const [currentColumnIndex, setCurrentColumnIndex] = useState(0)
  const canCycle = activeColumns < totalColumns
  const cycle = useCallback(() => {
    if (canCycle) {
      setCurrentColumnIndex((i) => (i + 1) % totalColumns)
    }
  }, [canCycle, totalColumns])
  const goBackward = useCallback(() => {
    if (canCycle) {
      setCurrentColumnIndex((i) => (i - 1 + totalColumns) % totalColumns)
    }
  }, [canCycle, totalColumns])

  const goToIndex = useCallback(
    (index: number) => {
      if (canCycle) {
        setCurrentColumnIndex(index % totalColumns)
      }
    },
    [canCycle, totalColumns]
  )

  const providerValue: InferContextType<typeof ColumnCarouselContext> = {
    currentColumnIndex,
    setCurrentColumnIndex,
    cycle,
    goForward: cycle,
    goBackward,
    goToIndex,
    activeColumns,
    totalColumns,
    canCycle,
  }

  return <ColumnCarouselContext.Provider value={providerValue}>{children}</ColumnCarouselContext.Provider>
}

export const useColumnCarouselContext = () => {
  return useContext(ColumnCarouselContext)
}

type ColumnCarouselProps = {
  children: ReactNode[]
} & InferComponentProps<typeof ColumnCarouselContainer>

/**
 * ColumnCarousel is used to display a subset of children at a time, cycling through them.  This is different from a
 * typical carousel in that there can be multiple carousels on the page each synced to the same column index.
 * Essentially this is multiple carousels controlled by a single provider.
 */
export const ColumnCarousel = ({ children, ...props }: ColumnCarouselProps) => {
  const { currentColumnIndex, activeColumns, totalColumns } = useColumnCarouselContext()

  const childrenArray = Array.isArray(children) ? children : [children]

  useEffect(() => {
    if (totalColumns !== childrenArray.length) {
      console.warn(
        `ColumnCarousel: ColumnCarouselProvider's totalColumns (${totalColumns}) does not match number of children (${childrenArray.length}).  This will cause issues when cycling.`
      )
    }
  }, [totalColumns, childrenArray.length])

  return (
    <ColumnCarouselContainer {...props}>
      {[...childrenArray.slice(currentColumnIndex), ...childrenArray.slice(0, currentColumnIndex)].slice(
        0,
        activeColumns
      )}
    </ColumnCarouselContainer>
  )
}
