import React, { forwardRef, useCallback, useState } from 'react'
import cx from 'classnames'
import moment from 'moment'
import {
  DayOfWeekShape,
  DayPickerRangeController,
  DayPickerSingleDateController,
} from 'react-dates'
import DatePickerHeaderComponent from './DatePickerHeader/DatePickerHeader'
import DateNavigationItem from './DateNavigationItem/DateNavigationItem'
import YearPicker from './YearPicker/YearPicker'
import { DatePickerType, DAY_SIZE, WEEK_FIRST_DAY } from './constants'
import { Direction, FocusInput, Moment, RangeDate } from './types'
import L3ComponentProps from '../../types/L3ComponentProps'
import L3Component from '../../types/L3Component'
import { getTestId } from '../../tests/test-ids-utils'
import { ComponentDefaultTestId } from '../../tests/constants'
import { NOOP } from '../../utils/function-utils'

import Button from '../Button/Button'
import Typography from '../typography/Typography'
import { TypographySizes, TypographyTypes } from '../typography/TypographyConstants'

interface DatePickerProps extends L3ComponentProps {
  /** set the first day of the week to display */
  firstDayOfWeek?: DayOfWeekShape
  /** current start date */
  date?: Moment
  /** current end date */
  endDate?: Moment
  /** on date selected callback */
  onPickDate?: (date: Moment | RangeDate) => void
  /** hide the month navigations keys */
  hideNavigationKeys?: boolean
  /** show days outside the cuurent month view */
  enableOutsideDays?: boolean
  /** show week number column */
  showWeekNumber?: boolean
  /** set the size of single day element */
  daySize?: number
  /** determine if day should be disabled */
  shouldBlockDay?: (date: Moment) => boolean
  /** date range mode*/
  range?: boolean
  /** number of month to display*/
  numberOfMonths?: number
  /** determine if year should be disabled */
  shouldBlockYear?: (year: number) => boolean
  /** determine if date range should be disabled */
  shouldBlockRange?: (date: Moment) => boolean

  onClear?: () => void
  onApply?: () => void
  buttonFooter?: boolean
  kind?: DatePickerType
}

// eslint-disable-next-line react/display-name
const DatePicker: L3Component<DatePickerProps, HTMLElement> = forwardRef<
  HTMLElement,
  DatePickerProps
>(
  (
    {
      id,
      className,
      firstDayOfWeek = WEEK_FIRST_DAY,
      daySize = DAY_SIZE,
      range = false,
      shouldBlockDay,
      shouldBlockYear,
      numberOfMonths = 1,
      hideNavigationKeys = false,
      date,
      endDate,
      onPickDate,
      enableOutsideDays = false,
      showWeekNumber = false,
      shouldBlockRange,
      'data-testid': dataTestId,
      kind = 'primary',
      onClear,
      onApply,
      buttonFooter = true,
    },
    ref,
  ) => {
    const [focusedInput, setFocusedInput] = useState(FocusInput.startDate)
    const [isMonthYearSelection, setIsMonthYearSelection] = useState(false) //show Month/Year selection dropdown
    const [overrideDateForView, setOverrideDateForView] = useState<Moment | null>(null)

    const renderMonth = useCallback(
      ({ month }: { month: Moment }) => {
        return (
          <DatePickerHeaderComponent
            data-testid={dataTestId || getTestId(ComponentDefaultTestId.DATEPICKER_HEADER, id)}
            currentDate={month || moment()}
            isMonthYearSelection={isMonthYearSelection}
            onToggleMonthYearPicker={() => setIsMonthYearSelection(val => !val)}
            hideNavigationKeys={hideNavigationKeys}
          />
        )
      },
      [dataTestId, isMonthYearSelection, hideNavigationKeys, id],
    )

    const renderDay = useCallback(
      (day: Moment) => {
        const weekNumber = firstDayOfWeek === 0 ? day.clone().add(1, 'd').isoWeek() : day.isoWeek()
        return (
          <>
            <span>{weekNumber}</span> {day.format('D')}
          </>
        )
      },
      [firstDayOfWeek],
    )

    const changeCurrentDateFromMonthYearView = useCallback((date: Moment | null) => {
      setOverrideDateForView(date)
      setIsMonthYearSelection(false)
    }, [])

    const renderMonthYearSelection = useCallback(() => {
      return (
        <YearPicker
          data-testid={
            dataTestId || getTestId(ComponentDefaultTestId.DATEPICKER_YEAR_SELECTION, id)
          }
          selectedDate={date}
          isYearBlocked={shouldBlockYear}
          changeCurrentDate={changeCurrentDateFromMonthYearView}
        />
      )
    }, [dataTestId, shouldBlockYear, changeCurrentDateFromMonthYearView, date, id])

    const onDateRangeChange = useCallback(
      (date: RangeDate) => {
        if (focusedInput === FocusInput.startDate) {
          onPickDate({ ...date, endDate: null })
        } else {
          onPickDate(date)
        }
      },
      [focusedInput, onPickDate],
    )

    const onFocusChange = useCallback((focusedInput: FocusInput) => {
      setFocusedInput(focusedInput || FocusInput.startDate)
    }, [])

    const shouldShowNav = !hideNavigationKeys && !isMonthYearSelection
    return (
      <div
        data-testid={dataTestId || getTestId(ComponentDefaultTestId.DATEPICKER, id)}
        ref={ref}
        id={id}
      >
        {range ? (
          <DayPickerRangeController
            key={`${overrideDateForView?.toString()}`}
            renderDayContents={showWeekNumber ? renderDay : undefined}
            firstDayOfWeek={firstDayOfWeek}
            hideKeyboardShortcutsPanel
            startDate={date}
            endDate={endDate}
            onDatesChange={onDateRangeChange}
            focusedInput={focusedInput}
            minimumNights={0}
            onFocusChange={onFocusChange}
            navPrev={shouldShowNav ? <DateNavigationItem kind={Direction.prev} /> : <div />}
            navNext={shouldShowNav ? <DateNavigationItem kind={Direction.next} /> : <div />}
            daySize={daySize}
            isOutsideRange={shouldBlockRange}
            isDayBlocked={shouldBlockDay}
            renderMonthElement={renderMonth}
            enableOutsideDays={enableOutsideDays || showWeekNumber}
            numberOfMonths={numberOfMonths}
            initialVisibleMonth={() => overrideDateForView || date || moment()}
          />
        ) : (
          <DayPickerSingleDateController
            key={`${overrideDateForView?.toString()}`}
            renderDayContents={showWeekNumber ? renderDay : undefined}
            firstDayOfWeek={firstDayOfWeek}
            hideKeyboardShortcutsPanel
            onFocusChange={NOOP}
            numberOfMonths={numberOfMonths}
            date={date}
            onDateChange={(date: Moment) => onPickDate(date)}
            navPrev={shouldShowNav ? <DateNavigationItem kind={Direction.prev} /> : <div />}
            navNext={shouldShowNav ? <DateNavigationItem kind={Direction.next} /> : <div />}
            focused={true}
            renderMonthElement={renderMonth}
            enableOutsideDays={enableOutsideDays || showWeekNumber}
            daySize={daySize}
            isDayBlocked={shouldBlockDay}
            initialVisibleMonth={() => overrideDateForView || date || moment()}
          />
        )}
        {isMonthYearSelection && renderMonthYearSelection()}

        <div>
          <Button
            size={Button.sizes?.SMALL}
            kind={Button.kinds?.TERTIARY}
            onClick={() => {
              if (range) {
                setFocusedInput(FocusInput.startDate)
              }
              onClear()
            }}
          >
            <div>
              <Typography value='Clear' type={TypographyTypes.LABEL} size={TypographySizes.sm} />
            </div>
          </Button>
          <Button size={Button.sizes?.SMALL} onClick={onApply}>
            <div>
              <Typography value='Apply' type={TypographyTypes.LABEL} size={TypographySizes.sm} />
            </div>
          </Button>
        </div>
      </div>
    )
  },
)

Object.assign(DatePicker, {
  defaultTestId: ComponentDefaultTestId.DATEPICKER,
})
export default DatePicker
