import React, {useRef, useReducer, useEffect} from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import Button, {BUTTON_TYPE} from '@propellerads/button';
import {smallDevices} from '@propellerads/stylevariables';
import 'react-dates/initialize';
import {DayPickerRangeController} from 'react-dates';
import {
  START_DATE, END_DATE, HORIZONTAL_ORIENTATION, VERTICAL_SCROLLABLE, VERTICAL_ORIENTATION,
} from 'react-dates/constants';

import {
  StyledDatePicker,
  DatePickerSelector,
  DatePickerCalendar,
  DatePickerFooter,
  CommonStyles,
  SELECTOR_ALIGN,
  SELECTOR_POSITION,
} from './style';

import DEFAULT_PRESET_PROPS from './defaultProps';
import {getActivePresetByDateRange, isDateValid, getMomentDate} from './helpers';

import DatePickerPresets from './DatePickerPresets';
import DatePickerControls from './DatePickerControls';

const SET_START_DATE = 'SET_START_DATE';
const SET_END_DATE = 'SET_END_DATE';
const SET_FOCUSED_INPUT = 'SET_FOCUSED_INPUT';
const SET_IS_EXPANDED = 'SET_IS_EXPANDED';

const isMobile = document.body.clientWidth < smallDevices;

function getFocusedInput(disabled) {
  switch (disabled) {
    case true:
      return null;

    case START_DATE:
      return END_DATE;

    case END_DATE:
    default:
      return START_DATE;
  }
}

function useOnClickOutside(ref, handler) {
  useEffect(() => {
    const listener = (event) => {
      if (!ref.current || ref.current.contains(event.target)) {
        return;
      }

      handler(event);
    };

    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);

    return () => {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
  }, [ref, handler]);
}

function datePickerReducer(state, action) {
  switch (action.type) {
    case SET_START_DATE:
      return {
        ...state,
        localStartDate: action.payload,
      };

    case SET_END_DATE:
      return {
        ...state,
        localEndDate: action.payload,
      };

    case SET_FOCUSED_INPUT:
      return {
        ...state,
        focusedInput: action.payload,
      };

    case SET_IS_EXPANDED:
      return {
        ...state,
        isExpanded: action.payload,
      };

    default:
      return state;
  }
}

const DatePicker = (props) => {
  const {
    elementId,
    startDate,
    endDate,
    minDate,
    maxDate,
    onDatesChange,
    presets,
    startDatePlaceholderText,
    endDatePlaceholderText,
    applyButtonText,
    selectorAlign,
    isShowFastControl,
    monthText,
    firstDayOfWeek,
    isOnTopPresets,
    isNullable,
    position,
    disabled,
    liteMode,
    orientation,
  } = props;

  const [datePickerState, dispatch] = useReducer(datePickerReducer, {
    localStartDate: startDate,
    localEndDate: endDate,
    focusedInput: getFocusedInput(disabled),
    isExpanded: false,
  });

  const datePickerRef = useRef(null);
  const controlRef = useRef(null);
  const finalOrientation = isMobile ? VERTICAL_SCROLLABLE : orientation || HORIZONTAL_ORIENTATION;

  function changeExpandedState(value) {
    dispatch({
      type: SET_IS_EXPANDED,
      payload: value,
    });
  }

  function showPicker() {
    changeExpandedState(true);
  }

  function hidePicker() {
    changeExpandedState(false);
  }

  function resetLocalState() {
    dispatch({
      type: SET_START_DATE,
      payload: startDate,
    });

    dispatch({
      type: SET_END_DATE,
      payload: endDate,
    });
  }

  function onFocusChange(focusedInput) {
    dispatch({
      type: SET_FOCUSED_INPUT,
      payload: !focusedInput
        ? getFocusedInput(disabled)
        : focusedInput,
    });
  }

  function onApplyButtonClick() {
    onDatesChange({
      startDate: datePickerState.localStartDate,
      endDate: datePickerState.localEndDate,
    });
    hidePicker();
  }

  function onPresetClickHandler(preset) {
    onDatesChange({
      startDate: preset.startDate,
      endDate: preset.endDate,
    });

    dispatch({
      type: SET_FOCUSED_INPUT,
      payload: getFocusedInput(disabled),
    });

    hidePicker();
  }

  function onDatesPickerChange(newValue) {
    dispatch({
      type: SET_START_DATE,
      payload: newValue.startDate,
    });

    dispatch({
      type: SET_END_DATE,
      payload: newValue.startDate > newValue.endDate ? newValue.startDate : newValue.endDate,
    });

    if (liteMode && (newValue.endDate > newValue.startDate || disabled)) {
      onDatesChange({
        startDate: newValue.startDate,
        endDate: newValue.endDate,
      });
      hidePicker();
    }
  }

  function onStartDateInputChange(value) {
    if (isNullable && !value) {
      dispatch({
        type: SET_START_DATE,
        payload: undefined,
      });
      return;
    }

    if (!isDateValid(value)) {
      return;
    }

    dispatch({
      type: SET_START_DATE,
      payload: getMomentDate(value),
    });
  }

  function onEndDateInputChange(value) {
    if (isNullable && !value) {
      dispatch({
        type: SET_END_DATE,
        payload: undefined,
      });
      return;
    }

    if (!isDateValid(value)) {
      return;
    }

    dispatch({
      type: SET_END_DATE,
      payload: getMomentDate(value),
    });
  }

  function onStartDateInputFocus() {
    dispatch({
      type: SET_FOCUSED_INPUT,
      payload: disabled ? getFocusedInput(disabled) : START_DATE,
    });

    showPicker();
  }

  function onEndDateInputFocus() {
    dispatch({
      type: SET_FOCUSED_INPUT,
      payload: disabled ? getFocusedInput(disabled) : END_DATE,
    });

    showPicker();
  }

  function getValidStartDate(currentStartDate, currentEndDate) {
    return moment.min([
      moment.max([
        currentStartDate,
        minDate,
      ].filter(Boolean)),
      currentEndDate,
      maxDate,
    ].filter(Boolean));
  }

  function getValidEndDate(currentEndDate, currentStartDate) {
    return moment.min([
      moment.max([
        currentStartDate,
        currentEndDate,
        minDate,
      ].filter(Boolean)),
      maxDate,
    ].filter(Boolean));
  }

  function onArrowButtonClick(newDates) {
    onDatesChange({
      startDate: getValidStartDate(newDates.startDate, newDates.endDate),
      endDate: getValidEndDate(newDates.endDate, newDates.startDate),
    });

    hidePicker();
  }

  function onEnterKeyDown() {
    const {localStartDate, localEndDate} = datePickerState;

    onDatesChange({
      startDate: localStartDate && getValidStartDate(localStartDate, localEndDate),
      endDate: localEndDate && getValidEndDate(localEndDate, localStartDate),
    });

    hidePicker();
  }

  function onEscKeyDown() {
    resetLocalState();
    hidePicker();
  }

  function onOutsideClick() {
    if (datePickerState.isExpanded) {
      resetLocalState();
      hidePicker();
    }
  }

  function onStartDateInputBlur() {
    const {localStartDate, localEndDate} = datePickerState;

    dispatch({
      type: SET_START_DATE,
      payload: localStartDate && getValidStartDate(localStartDate, localEndDate),
    });
  }

  function onEndDateInputBlur() {
    const {localStartDate, localEndDate} = datePickerState;

    dispatch({
      type: SET_END_DATE,
      payload: localEndDate && getValidEndDate(localEndDate, localStartDate),
    });
  }

  function getMonthText(month) {
    const number = +month.format('M');
    const name = monthText?.[number] || month.format('MMMM');

    return `${name} ${month.format('YYYY')}`;
  }

  function isDayBlocked(current) {
    const currentStartOfDay = current.startOf('day');
    const isBlockedByMinDate = !!minDate
        && (moment.duration(currentStartOfDay.diff(minDate))
          .asDays() < 0);
    const isBlockedByMaxDate = !!maxDate
        && (moment.duration(currentStartOfDay.diff(maxDate))
          .asDays() > 0);
    return isBlockedByMinDate || isBlockedByMaxDate;
  }

  useEffect(() => {
    dispatch({
      type: SET_START_DATE,
      payload: startDate,
    });
  }, [startDate]);

  useEffect(() => {
    dispatch({
      type: SET_END_DATE,
      payload: endDate,
    });
  }, [endDate]);

  useEffect(() => {
    dispatch({
      type: SET_FOCUSED_INPUT,
      payload: getFocusedInput(disabled),
    });

    if (disabled === true) {
      hidePicker();
    }
  }, [disabled]);

  const activePreset = (presets && presets.length > 0)
    ? getActivePresetByDateRange(presets, startDate, endDate)
    : undefined;

  useOnClickOutside(datePickerRef, () => onOutsideClick());

  return (
    <StyledDatePicker ref={datePickerRef}>
      <CommonStyles />
      <DatePickerControls
        isExpanded={datePickerState.isExpanded}
        elementId={elementId}
        activePreset={activePreset}
        startDate={datePickerState.localStartDate}
        endDate={datePickerState.localEndDate}
        onEnterKeyDown={onEnterKeyDown}
        onEscKeyDown={onEscKeyDown}
        onArrowButtonClick={onArrowButtonClick}
        onStartDateInputFocus={onStartDateInputFocus}
        onEndDateInputFocus={onEndDateInputFocus}
        onStartDateInputBlur={onStartDateInputBlur}
        onEndDateInputBlur={onEndDateInputBlur}
        onStartDateInputChange={onStartDateInputChange}
        onEndDateInputChange={onEndDateInputChange}
        startDatePlaceholderText={startDatePlaceholderText}
        endDatePlaceholderText={endDatePlaceholderText}
        isShowFastControl={isShowFastControl}
        disabled={disabled}
      />
      {datePickerState.isExpanded && (
        <DatePickerSelector
          align={selectorAlign}
          isMobile={isMobile}
          isOnTopPresets={isOnTopPresets}
          position={position}
        >
          {presets && presets.length > 0 && (
          <DatePickerPresets
            activePreset={activePreset}
            onPresetClickHandler={onPresetClickHandler}
            presets={presets}
            startDate={startDate}
            endDate={endDate}
            elementId={elementId}
            isMobile={isMobile}
            isOnTop={isOnTopPresets}
          />
          )}
          <DatePickerCalendar>
            <DayPickerRangeController
              ref={controlRef}
              firstDayOfWeek={firstDayOfWeek}
              numberOfMonths={isMobile ? 12 : 2}
              minimumNights={0}
              orientation={finalOrientation}
              minDate={minDate}
              maxDate={maxDate}
              disabled={disabled}
              isDayBlocked={isDayBlocked}
              startDate={datePickerState.localStartDate}
              endDate={datePickerState.localEndDate}
              focusedInput={datePickerState.focusedInput}
              onDatesChange={onDatesPickerChange}
              onFocusChange={onFocusChange}
              renderMonthText={getMonthText}
              hideKeyboardShortcutsPanel
              noBorder
            />
            {!liteMode && (
            <DatePickerFooter>
              <Button
                elementId="date-picker-footer-button"
                type={BUTTON_TYPE.ADVANCED}
                onClick={onApplyButtonClick}
              >
                {applyButtonText}
              </Button>
            </DatePickerFooter>
            )}
          </DatePickerCalendar>
        </DatePickerSelector>
      )}
    </StyledDatePicker>
  );
};

DatePicker.propTypes = {
  startDate: PropTypes.object, // - moment object
  endDate: PropTypes.object, // - moment object
  minDate: PropTypes.object, // - moment object
  maxDate: PropTypes.object, // - moment object
  elementId: PropTypes.string.isRequired,
  onDatesChange: PropTypes.func.isRequired,
  startDatePlaceholderText: PropTypes.string,
  endDatePlaceholderText: PropTypes.string,
  applyButtonText: PropTypes.string,
  presets: PropTypes.arrayOf(PropTypes.shape(DEFAULT_PRESET_PROPS)),
  selectorAlign: PropTypes.oneOf([SELECTOR_ALIGN.LEFT, SELECTOR_ALIGN.RIGHT]),
  isShowFastControl: PropTypes.bool,
  monthText: PropTypes.objectOf(PropTypes.string),
  firstDayOfWeek: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6]),
  isOnTopPresets: PropTypes.bool,
  isNullable: PropTypes.bool,
  position: PropTypes.oneOf([SELECTOR_POSITION.TOP, SELECTOR_POSITION.BOTTOM]),
  disabled: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf([START_DATE, END_DATE])]),
  liteMode: PropTypes.bool,
  orientation: PropTypes.oneOf([HORIZONTAL_ORIENTATION, VERTICAL_ORIENTATION, VERTICAL_SCROLLABLE]),
};

DatePicker.defaultProps = {
  startDate: undefined,
  endDate: undefined,
  minDate: undefined,
  maxDate: undefined,
  startDatePlaceholderText: 'Start Date',
  endDatePlaceholderText: 'End Date',
  applyButtonText: 'Apply',
  presets: undefined,
  selectorAlign: SELECTOR_ALIGN.LEFT,
  isShowFastControl: true,
  monthText: null,
  firstDayOfWeek: 0,
  isOnTopPresets: false,
  isNullable: false,
  position: SELECTOR_POSITION.BOTTOM,
  disabled: false,
  liteMode: false,
  orientation: undefined,
};

export default DatePicker;

export {
  SELECTOR_ALIGN,
  SELECTOR_POSITION,
  START_DATE,
  END_DATE,
  HORIZONTAL_ORIENTATION,
  VERTICAL_SCROLLABLE,
  VERTICAL_ORIENTATION,
};
