import React, { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
import { dateToUnix, getEndOfDay, getStartOfDay, areDatesOnSameDay, removeTime } from './utils';
import { CalendarContext } from './calendar-context';
import { CalendarActionKind, CONTAINER_HEIGHT, WEEKDAYS_HEIGHT } from './enums';
import Calendar from './components/calendar';
import { useDeepCompareMemo } from './utils';
import dayjs from 'dayjs';
import localeData from 'dayjs/plugin/localeData';
import relativeTime from 'dayjs/plugin/relativeTime';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import duration from 'dayjs/plugin/duration';
import { usePrevious } from './hooks/use-previous';
dayjs.extend(localeData);
dayjs.extend(relativeTime);
dayjs.extend(localizedFormat);
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(duration);
const DateTimePicker = props => {
  const {
    mode = 'single',
    locale = 'en',
    numerals = 'latn',
    timeZone,
    showOutsideDays = false,
    timePicker = false,
    firstDayOfWeek,
    // startYear,
    // endYear,
    minDate,
    maxDate,
    enabledDates,
    disabledDates,
    date,
    startDate,
    endDate,
    dates,
    min,
    max,
    onChange,
    initialView = 'day',
    containerHeight = CONTAINER_HEIGHT,
    weekdaysHeight = WEEKDAYS_HEIGHT,
    style = {},
    className = '',
    classNames = {},
    styles = {},
    navigationPosition,
    weekdaysFormat = 'min',
    monthsFormat = 'full',
    monthCaptionFormat = 'full',
    multiRangeMode,
    hideHeader,
    hideWeekdays,
    disableMonthPicker,
    disableYearPicker,
    components = {},
    month,
    year,
    onMonthChange = () => {},
    onYearChange = () => {},
    use12Hours
  } = props;
  dayjs.tz.setDefault(timeZone);
  dayjs.locale(locale);
  const prevTimezone = usePrevious(timeZone);
  const initialCalendarView = useMemo(() => mode !== 'single' && initialView === 'time' ? 'day' : initialView, [mode, initialView]);
  const firstDay = useMemo(() => firstDayOfWeek && firstDayOfWeek > 0 && firstDayOfWeek <= 6 ? firstDayOfWeek : 0, [firstDayOfWeek]);
  const initialState = useMemo(() => {
    let initialDate = dayjs().tz(timeZone);
    if (mode === 'single' && date) {
      initialDate = dayjs(date);
    }
    if (mode === 'range' && startDate) {
      initialDate = dayjs(startDate);
    }
    if (mode === 'multiple' && dates && dates.length > 0) {
      initialDate = dayjs(dates[0]);
    }
    if (minDate && initialDate.isBefore(minDate)) {
      initialDate = dayjs(minDate);
    }
    if (month !== undefined && month && month >= 0 && month <= 11) {
      initialDate = initialDate.month(month);
    }
    if (year !== undefined && year >= 0) {
      initialDate = initialDate.year(year);
    }
    let _date = date ? dayjs(date) : date;
    if (_date && maxDate && dayjs(_date).isAfter(maxDate)) {
      _date = dayjs(maxDate);
    }
    if (_date && minDate && dayjs(_date).isBefore(minDate)) {
      _date = dayjs(minDate);
    }
    let start = startDate ? dayjs(startDate) : startDate;
    if (start && maxDate && dayjs(start).isAfter(maxDate)) {
      start = dayjs(maxDate);
    }
    if (start && minDate && dayjs(start).isBefore(minDate)) {
      start = dayjs(minDate);
    }
    let end = endDate ? dayjs(endDate) : endDate;
    if (end && maxDate && dayjs(end).isAfter(maxDate)) {
      end = dayjs(maxDate);
    }
    if (end && minDate && dayjs(end).isBefore(minDate)) {
      end = dayjs(minDate);
    }
    return {
      date: _date,
      startDate: start,
      endDate: end,
      dates,
      calendarView: initialCalendarView,
      currentDate: initialDate,
      currentYear: initialDate.year()
    };
  }, [mode, date, startDate, endDate, dates, minDate, maxDate, month, year, timeZone, initialCalendarView]);
  const [state, dispatch] = useReducer((prevState, action) => {
    switch (action.type) {
      case CalendarActionKind.SET_CALENDAR_VIEW:
        return {
          ...prevState,
          calendarView: action.payload
        };
      case CalendarActionKind.CHANGE_CURRENT_DATE:
        return {
          ...prevState,
          currentDate: action.payload
        };
      case CalendarActionKind.CHANGE_CURRENT_YEAR:
        return {
          ...prevState,
          currentYear: action.payload
        };
      case CalendarActionKind.CHANGE_SELECTED_DATE:
        const {
          date: selectedDate
        } = action.payload;
        return {
          ...prevState,
          date: selectedDate,
          currentDate: selectedDate
        };
      case CalendarActionKind.CHANGE_SELECTED_RANGE:
        const {
          startDate: start,
          endDate: end
        } = action.payload;
        return {
          ...prevState,
          startDate: start,
          endDate: end
        };
      case CalendarActionKind.CHANGE_SELECTED_MULTIPLE:
        const {
          dates: selectedDates
        } = action.payload;
        return {
          ...prevState,
          dates: selectedDates
        };
    }
  }, initialState);
  const stateRef = useRef(state);
  stateRef.current = state;
  useEffect(() => {
    if (prevTimezone !== timeZone) {
      const newDate = dayjs().tz(timeZone);
      dispatch({
        type: CalendarActionKind.CHANGE_CURRENT_DATE,
        payload: newDate
      });
    }
  }, [timeZone, prevTimezone]);
  useEffect(() => {
    if (mode === 'single') {
      let _date = (date && (timePicker ? dayjs.tz(date, timeZone) : getStartOfDay(dayjs.tz(date, timeZone)))) ?? date;
      if (_date && maxDate && dayjs.tz(_date, timeZone).isAfter(maxDate)) {
        _date = dayjs.tz(maxDate, timeZone);
      }
      if (_date && minDate && dayjs.tz(_date, timeZone).isBefore(minDate)) {
        _date = dayjs.tz(minDate, timeZone);
      }
      dispatch({
        type: CalendarActionKind.CHANGE_SELECTED_DATE,
        payload: {
          date: _date
        }
      });
      if (prevTimezone !== timeZone) {
        onChange({
          date: _date ? dayjs(_date).toDate() : _date
        });
      }
    } else if (mode === 'range') {
      let start = startDate ? dayjs.tz(startDate, timeZone) : startDate;
      if (start && maxDate && dayjs.tz(start, timeZone).isAfter(maxDate)) {
        start = dayjs.tz(maxDate, timeZone);
      }
      if (start && minDate && dayjs.tz(start, timeZone).isBefore(minDate)) {
        start = dayjs.tz(minDate, timeZone);
      }
      let end = endDate ? dayjs.tz(endDate, timeZone) : endDate;
      if (end && maxDate && dayjs.tz(end, timeZone).isAfter(maxDate)) {
        end = dayjs.tz(maxDate, timeZone);
      }
      if (end && minDate && dayjs.tz(end, timeZone).isBefore(minDate)) {
        end = dayjs.tz(minDate, timeZone);
      }
      dispatch({
        type: CalendarActionKind.CHANGE_SELECTED_RANGE,
        payload: {
          startDate: start,
          endDate: end
        }
      });
      if (prevTimezone !== timeZone) {
        onChange({
          startDate: start ? dayjs(start).toDate() : start,
          endDate: end ? dayjs(end).toDate() : end
        });
      }
    } else if (mode === 'multiple') {
      const _dates = dates === null || dates === void 0 ? void 0 : dates.map(date => dayjs(date).tz(timeZone));
      dispatch({
        type: CalendarActionKind.CHANGE_SELECTED_MULTIPLE,
        payload: {
          dates: _dates
        }
      });
      if (prevTimezone !== timeZone) {
        onChange({
          dates: _dates.map(item => dayjs(item).toDate()),
          change: 'updated'
        });
      }
    }
  }, [mode, date, startDate, endDate, dates, minDate, maxDate, timePicker, prevTimezone, timeZone]);
  const setCalendarView = useCallback(view => {
    dispatch({
      type: CalendarActionKind.SET_CALENDAR_VIEW,
      payload: view
    });
  }, []);
  const onSelectDate = useCallback(selectedDate => {
    if (onChange) {
      if (mode === 'single') {
        const newDate = timePicker ? dayjs.tz(selectedDate, timeZone) : dayjs.tz(getStartOfDay(selectedDate), timeZone);
        dispatch({
          type: CalendarActionKind.CHANGE_CURRENT_DATE,
          payload: newDate
        });
        onChange({
          date: newDate ? dayjs(newDate).toDate() : newDate
        });
      } else if (mode === 'range') {
        // set time to 00:00:00
        let start = removeTime(stateRef.current.startDate, timeZone);
        let end = removeTime(stateRef.current.endDate, timeZone);
        const selected = removeTime(selectedDate, timeZone);
        let isStart = true;
        let isReset = false;
        if (dateToUnix(selected) !== dateToUnix(end) && dateToUnix(selected) >= dateToUnix(start) && dateToUnix(start) !== dateToUnix(end)) {
          isStart = false;
        } else if (start && dateToUnix(selected) === dateToUnix(start)) {
          isReset = true;
        }
        if (start && end) {
          if (dateToUnix(start) === dateToUnix(end) && dateToUnix(selected) > dateToUnix(start)) {
            isStart = false;
          }
          if (dateToUnix(selected) > dateToUnix(start) && dateToUnix(selected) === dateToUnix(end)) {
            end = undefined;
          }
        }
        if (start && !end && dateToUnix(selected) < dateToUnix(start)) {
          end = start;
        }
        if (isStart && end && (min || max)) {
          const numberOfDays = dayjs(end).diff(selected, 'day');
          if (max && numberOfDays > max || min && numberOfDays < min) {
            isStart = true;
            end = undefined;
          }
        }
        if (!isStart && start && (min || max)) {
          const numberOfDays = dayjs(selected).diff(start, 'day');
          if (dateToUnix(selected) === dateToUnix(start)) {
            isReset = true;
          } else if (max && numberOfDays > max || min && numberOfDays < min) {
            isStart = true;
            end = undefined;
          }
        }
        if (isReset) {
          onChange({
            startDate: undefined,
            endDate: undefined
          });
        } else {
          onChange({
            startDate: isStart ? dayjs(selected).toDate() : start ? dayjs.tz(start).toDate() : start,
            endDate: !isStart ? dayjs.tz(getEndOfDay(selected), timeZone).toDate() : end ? dayjs.tz(getEndOfDay(end), timeZone).toDate() : end
          });
        }
      } else if (mode === 'multiple') {
        const safeDates = stateRef.current.dates || [];
        const newDate = dayjs(selectedDate, timeZone).startOf('day');
        const exists = safeDates.some(ed => areDatesOnSameDay(ed, newDate));
        const newDates = exists ? safeDates.filter(ed => !areDatesOnSameDay(ed, newDate)) : [...safeDates, newDate];
        if (max && newDates.length > max) {
          return;
        }
        newDates.sort((a, b) => dayjs(a).isAfter(dayjs(b)) ? 1 : -1);
        const _dates = newDates.map(date => dayjs(date).tz(timeZone));
        onChange({
          dates: _dates.map(item => dayjs(item).toDate()),
          datePressed: newDate ? dayjs(newDate).toDate() : newDate,
          change: exists ? 'removed' : 'added'
        });
      }
    }
  }, [mode, timePicker, min, max, timeZone]);

  // set the active displayed month
  const onSelectMonth = useCallback(value => {
    const currentMonth = dayjs(stateRef.current.currentDate).month();
    const newDate = dayjs(stateRef.current.currentDate).month(value);

    // Only call onMonthChange if the month actually changed
    if (value !== currentMonth) {
      onMonthChange(value);
    }
    dispatch({
      type: CalendarActionKind.CHANGE_CURRENT_DATE,
      payload: newDate
    });
    setCalendarView('day');
  }, [setCalendarView, onMonthChange]);

  // set the active displayed year
  const onSelectYear = useCallback(value => {
    const currentYear = dayjs(stateRef.current.currentDate).year();
    const newDate = dayjs(stateRef.current.currentDate).year(value);

    // Only call onYearChange if the year actually changed
    if (value !== currentYear) {
      onYearChange(value);
    }
    dispatch({
      type: CalendarActionKind.CHANGE_CURRENT_DATE,
      payload: newDate
    });
    setCalendarView('day');
  }, [setCalendarView, onYearChange]);
  const onChangeMonth = useCallback(value => {
    const newDate = dayjs(stateRef.current.currentDate).add(value, 'month');
    dispatch({
      type: CalendarActionKind.CHANGE_CURRENT_DATE,
      payload: dayjs(newDate)
    });
  }, [stateRef, dispatch]);
  const onChangeYear = useCallback(value => {
    dispatch({
      type: CalendarActionKind.CHANGE_CURRENT_YEAR,
      payload: value
    });
  }, [dispatch]);
  useEffect(() => {
    if (month !== undefined && month >= 0 && month <= 11) {
      onSelectMonth(month);
    }
  }, [month]);
  useEffect(() => {
    if (year !== undefined && year >= 0) {
      onSelectYear(year);
    }
  }, [year]);
  const memoizedStyles = useDeepCompareMemo({
    ...styles
  }, [styles]);
  const memoizedClassNames = useDeepCompareMemo({
    ...classNames
  }, [classNames]);
  const memoizedComponents = useMemo(() => ({
    ...components
  }), [components]);
  const baseContextValue = useMemo(() => ({
    mode,
    locale,
    numerals,
    timeZone,
    showOutsideDays,
    timePicker,
    minDate,
    maxDate,
    min,
    max,
    enabledDates,
    disabledDates,
    firstDayOfWeek: firstDay,
    containerHeight,
    weekdaysHeight,
    navigationPosition,
    weekdaysFormat,
    monthsFormat,
    monthCaptionFormat,
    multiRangeMode,
    hideHeader,
    hideWeekdays,
    disableMonthPicker,
    disableYearPicker,
    style,
    className,
    use12Hours
  }), [mode, locale, numerals, timeZone, showOutsideDays, timePicker, minDate, maxDate, min, max, enabledDates, disabledDates, firstDay, containerHeight, weekdaysHeight, navigationPosition, weekdaysFormat, monthsFormat, monthCaptionFormat, multiRangeMode, hideHeader, hideWeekdays, disableMonthPicker, disableYearPicker, style, className, use12Hours]);
  const handlerContextValue = useMemo(() => ({
    setCalendarView,
    onSelectDate,
    onSelectMonth,
    onSelectYear,
    onChangeMonth,
    onChangeYear
  }), [setCalendarView, onSelectDate, onSelectMonth, onSelectYear, onChangeMonth, onChangeYear]);
  const styleContextValue = useMemo(() => ({
    classNames: memoizedClassNames,
    styles: memoizedStyles
  }), [memoizedClassNames, memoizedStyles]);
  const memoizedValue = useMemo(() => ({
    ...state,
    ...baseContextValue,
    ...handlerContextValue,
    ...styleContextValue,
    components: memoizedComponents
  }), [state, baseContextValue, handlerContextValue, styleContextValue, memoizedComponents]);
  return /*#__PURE__*/React.createElement(CalendarContext.Provider, {
    value: memoizedValue
  }, /*#__PURE__*/React.createElement(Calendar, null));
};
export default DateTimePicker;
//# sourceMappingURL=datetime-picker.js.map