import { generateDateByUTCValue, getDateUTCValue, sameYear, sameYearSameMonth } from '../utils'
import { CalendarMode, DateRange, FlowDirection, TimeItem, TimeUnitMode } from './calendar.types'
import { minDateTimestamp, viewYears } from './consts'

export const isExact = (mode?: CalendarMode) => mode === 'exact'
export const isFrom = (mode?: CalendarMode) => mode === 'from'
export const isTo = (mode?: CalendarMode) => mode === 'to'
export const isRange = (mode?: CalendarMode) => mode === 'range'

export const isDay = (timeUnitMode?: TimeUnitMode) => timeUnitMode === 'day'
export const isMonth = (timeUnitMode?: TimeUnitMode) => timeUnitMode === 'month'
export const isYear = (timeUnitMode?: TimeUnitMode) => timeUnitMode === 'year'

/**
 * Helper to get the next date item when iterating between two dates
 * @param date current date in the iteration
 * @param timeUnitMode time unit mode for day, month, year
 */
export const getNextDate = (date: Date, timeUnitMode?: TimeUnitMode) => {
  const { utcYear, utcMonth, utcDate } = getDateUTCValue(date)
  if (isDay(timeUnitMode)) return generateDateByUTCValue(utcYear, utcMonth, utcDate + 1)
  if (isMonth(timeUnitMode)) return generateDateByUTCValue(utcYear, utcMonth + 1, utcDate)

  return generateDateByUTCValue(utcYear + 1, utcMonth, utcDate)
}

export const processSelectMonth = (clickedDate: Date, mode: CalendarMode, boundaries?: DateRange) => {
  const date = clickedDate
  const minDateTime = new Date(minDateTimestamp)
  const { utcYear, utcMonth } = getDateUTCValue(clickedDate)
  if (isTo(mode)) {
    const selectDayInMonth = generateDateByUTCValue(utcYear, utcMonth + 1, 0).getUTCDate()

    if (boundaries?.endDate && sameYearSameMonth(clickedDate, boundaries?.endDate)) return boundaries?.endDate

    return generateDateByUTCValue(utcYear, utcMonth, selectDayInMonth)
  }

  if (isFrom(mode) && boundaries?.startDate && sameYearSameMonth(clickedDate, boundaries?.startDate))
    return boundaries?.startDate

  if (isExact(mode) && sameYear(clickedDate, minDateTime)) return minDateTime

  return boundaries?.startDate && date < boundaries?.startDate ? boundaries.startDate : date
}

/**
 * Helper that calcs what new date we should set as viewDate when a year is clicked
 *
 * @param clickedDate date of year clicked
 * @param newDate pre-processed potential new view date
 * @param mode calendar mode
 * @param boundaries boundaries of date range
 */
export const processSelectYear = (clickedData: Date, mode: CalendarMode, boundaries?: DateRange) => {
  const { utcYear, utcMonth } = getDateUTCValue(clickedData)
  const minDateTime = new Date(minDateTimestamp)
  const date = clickedData

  if (isTo(mode)) {
    const selectDayInMonth = generateDateByUTCValue(utcYear, utcMonth + 1, 0).getUTCDate()
    const selectMonthInYear = 11

    if (boundaries?.endDate && sameYear(clickedData, boundaries?.endDate)) return boundaries.endDate

    return generateDateByUTCValue(clickedData.getUTCFullYear(), selectMonthInYear, selectDayInMonth)
  }

  if (isFrom(mode) && boundaries?.startDate && sameYear(clickedData, boundaries?.startDate)) return boundaries.startDate

  if (isExact(mode) && sameYear(clickedData, minDateTime)) return minDateTime

  return boundaries?.startDate && date < boundaries?.startDate ? boundaries.startDate : date
}

export const processViewDate = (
  viewDate: Date,
  direction: FlowDirection,
  timeUnitMode: TimeUnitMode,
  calendarItems?: TimeItem[],
) => {
  const { utcYear, utcMonth } = getDateUTCValue(viewDate)
  if (isDay(timeUnitMode)) {
    const newMonth = generateDateByUTCValue(utcYear, utcMonth + (direction === 'next' ? 1 : -1))
    return newMonth
  }
  if (isMonth(timeUnitMode)) {
    const newMonth = generateDateByUTCValue(utcYear + (direction === 'next' ? 1 : -1), 0)
    return newMonth
  }

  if (calendarItems) {
    const date = calendarItems[direction === 'next' ? calendarItems.length - 1 : 0].date
    const year = date.getUTCFullYear()
    const newViewDate = generateDateByUTCValue(year + (direction === 'next' ? 1 : -viewYears), 0)
    return newViewDate
  }

  return null
}
