import React, { useEffect, useRef, useState } from 'react'

import Box from '../box'
import { omitThemingProps, useStyleConfig, vui } from '../core'
import { cs } from '../utils'
import { CalendarProps, DateRange, FlowDirection, TimeResult, TimeUnitMode } from './calendar.types'
import { CalendarHeader, DayPicker, MonthPicker, YearPicker } from './components'
import { useCalendar, useDefaultViewMonth } from './hooks'
import {
  isDay,
  isExact,
  isFrom,
  isMonth,
  isTo,
  isYear,
  processSelectMonth,
  processSelectYear,
  processViewDate,
} from './utils'

/** Displays calendar. */
export const Calendar = vui<'div', CalendarProps>((props, ref) => {
  const {
    boundaries,
    className,
    fixedNumberOfWeeks = false,
    disabled,
    isStartDate,
    mode,
    onSelectDates,
    selectedDates,
    ...rest
  } = omitThemingProps(props)
  const styles = useStyleConfig('Calendar', props)

  const currentDateRef = useRef(isFrom(mode) || isExact(mode) ? selectedDates?.startDate : selectedDates?.endDate)

  /**
   * Effect to reset viewDate when selectedDates changes (onReset)
   */
  useEffect(() => {
    currentDateRef.current = isFrom(mode) || isExact(mode) ? selectedDates?.startDate : selectedDates?.endDate
    currentDateRef.current && setViewDate(currentDateRef.current)
  }, [selectedDates])

  const calendarRef = useRef<TimeResult | null>(null)

  const defaultViewMonth = useDefaultViewMonth({
    boundaries,
    selectedDate: currentDateRef.current,
    isStartDate,
  })

  const [timeUnitMode, setTimeUnitMode] = useState<TimeUnitMode>('day')
  const [viewDate, setViewDate] = useState<Date>(defaultViewMonth)

  /**
   * Handles clicking next/prev in header
   */
  const onChangeViewItem = (direction: FlowDirection) => {
    const newViewMonth = processViewDate(viewDate, direction, timeUnitMode, calendarRef?.current?.items)
    newViewMonth && setViewDate(newViewMonth)
  }

  /**
   * Handles clicking on a day
   */
  const onSelectDay = (clickedDate: Date) => () => {
    updateDateState(clickedDate)
    setViewDate(clickedDate)
  }

  /**
   * Handles clicking on a month
   */
  const onSelectMonth = (clickedDate: Date) => () => {
    const processedNewDate = processSelectMonth(clickedDate, mode, boundaries)
    updateDateState(processedNewDate)
    setViewDate(processedNewDate)
    setTimeUnitMode('day')
  }

  /**
   * Handles clicking on a year
   */
  const onSelectYear = (clickedDate: Date) => () => {
    const processedNewDate = processSelectYear(clickedDate, mode, boundaries)
    updateDateState(processedNewDate)
    setViewDate(processedNewDate)
    setTimeUnitMode('month')
  }

  /**
   * Send update date to parent component
   */
  const updateDateState = (newDate: Date) => {
    const dateRange: DateRange = { ...selectedDates }

    if (dateRange) {
      if (isTo(mode)) dateRange.endDate = newDate
      else dateRange.startDate = newDate
    }

    if (newDate && !disabled) onSelectDates && onSelectDates(dateRange)
  }

  const calendar = (calendarRef.current = useCalendar({
    boundaries,
    mode,
    selectedDates,
    timeUnitMode,
    viewDate,
    fixedNumberOfWeeks,
  }))

  return (
    <Box
      className={cs('vui-calendar', className)}
      column
      cursor="default"
      minW={280}
      position="relative"
      ref={ref}
      w="fit-content"
      {...styles}
      {...rest}
    >
      <CalendarHeader
        nextDisabled={calendar.nextDisabled}
        onChangeViewItem={onChangeViewItem}
        onSetTimeUnitMode={(mode: TimeUnitMode) => setTimeUnitMode(mode)}
        prevDisabled={calendar.prevDisabled}
        timeUnitMode={timeUnitMode}
        viewMonth={viewDate}
      />
      {isDay(timeUnitMode) && <DayPicker calendar={calendar} onSelectDay={onSelectDay} />}
      {isMonth(timeUnitMode) && <MonthPicker calendar={calendar} onSelectMonth={onSelectMonth} />}
      {isYear(timeUnitMode) && <YearPicker calendar={calendar} onSelectYear={onSelectYear} />}
    </Box>
  )
})

Calendar.displayName = 'Calendar'

export default Calendar
