function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from 'react';
import { uid } from 'react-uid';
import { MaskedInput } from '../input';
import { Popover, PLACEMENT, ACCESSIBILITY_TYPE } from '../popover';
import Calendar from './calendar';
import { getOverrides, mergeOverrides } from '../helpers/overrides';
import getInterpolatedString from '../helpers/i18n-interpolation';
import { LocaleContext } from '../locale';
import { StyledInputWrapper, StyledInputLabel, StyledStartDate, StyledEndDate } from './styled-components';
import DateHelpers from './utils/date-helpers';
import dateFnsAdapter from './utils/date-fns-adapter';
import { INPUT_ROLE, RANGED_CALENDAR_BEHAVIOR } from './constants';
import { Button, KIND, SIZE } from '../button';
import { ChevronDown, Calendar as CalendarIcon } from '../icon';
import { colors } from '../tokens';
export const DEFAULT_DATE_FORMAT = 'yyyy/MM/dd';
const INPUT_DELIMITER = '–'; // @ts-ignore

const combineSeparatedInputs = (newInputValue, prevCombinedInputValue = '', inputRole) => {
  let inputValue = newInputValue;
  const [prevStartDate = '', prevEndDate = ''] = prevCombinedInputValue.split(` ${INPUT_DELIMITER} `);

  if (inputRole === INPUT_ROLE.startDate && prevEndDate) {
    inputValue = `${inputValue} ${INPUT_DELIMITER} ${prevEndDate}`;
  }

  if (inputRole === INPUT_ROLE.endDate) {
    inputValue = `${prevStartDate} ${INPUT_DELIMITER} ${inputValue}`;
  }

  return inputValue;
};

export default class Datepicker extends React.Component {
  constructor(props) {
    super(props); // @ts-ignore

    _defineProperty(this, "calendar", void 0);

    _defineProperty(this, "dateHelpers", void 0);

    _defineProperty(this, "handleChange", date => {
      const onChange = this.props.onChange;
      const onRangeChange = this.props.onRangeChange;

      if (Array.isArray(date)) {
        if (onChange && date.every(Boolean)) {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          onChange({
            date: date
          });
        }

        if (onRangeChange) {
          onRangeChange({
            date: [...date]
          });
        }
      } else {
        if (onChange) {
          onChange({
            date
          });
        }

        if (onRangeChange) {
          onRangeChange({
            date
          });
        }
      }
    });

    _defineProperty(this, "onCalendarSelect", data => {
      let isOpen = false;
      let isPseudoFocused = false;
      let calendarFocused = false;
      let nextDate = data.date;

      if (Array.isArray(nextDate) && this.props.range) {
        if (!nextDate[0] || !nextDate[1]) {
          isOpen = true;
          isPseudoFocused = true; // @ts-ignore

          calendarFocused = null;
        } else if (nextDate[0] && nextDate[1]) {
          const [start, end] = nextDate;

          if (this.dateHelpers.isAfter(start, end)) {
            if (this.hasLockedBehavior()) {
              nextDate = this.props.value;
              isOpen = true;
            } else {
              nextDate = [start, start];
            }
          } else if (this.dateHelpers.dateRangeIncludesDates(nextDate, this.props.excludeDates)) {
            nextDate = this.props.value;
            isOpen = true;
          }

          if (this.state.lastActiveElm) {
            this.state.lastActiveElm.focus();
          }
        }
      } else if (this.state.lastActiveElm) {
        this.state.lastActiveElm.focus();
      } // Time selectors previously caused the calendar popover to close.
      // The check below refrains from closing the popover if only times changed.


      const onlyTimeChanged = (prev, next) => {
        if (!prev || !next) return false;
        const p = this.dateHelpers.format(prev, 'keyboardDate');
        const n = this.dateHelpers.format(next, 'keyboardDate');

        if (p === n) {
          return this.dateHelpers.getHours(prev) !== this.dateHelpers.getHours(next) || this.dateHelpers.getMinutes(prev) !== this.dateHelpers.getMinutes(next);
        }

        return false;
      };

      const prevValue = this.props.value;

      if (Array.isArray(nextDate) && Array.isArray(prevValue)) {
        if (nextDate.some((d, i) => onlyTimeChanged(prevValue[i], d))) {
          isOpen = true;
        }
      } else if (!Array.isArray(nextDate) && !Array.isArray(prevValue)) {
        if (onlyTimeChanged(prevValue, nextDate)) {
          isOpen = true;
        }
      } // If nextDate is an array but the datepicker is not ranged, we assign
      // nextDate directly to the Date value to avoid formatting issues


      if (Array.isArray(nextDate) && !this.props.range) {
        nextDate = nextDate[0];
      }

      this.setState({
        isOpen,
        isPseudoFocused,
        ...(calendarFocused === null ? {} : {
          calendarFocused
        }),
        inputValue: this.formatDisplayValue(nextDate)
      });
      this.handleChange(nextDate);
    });

    _defineProperty(this, "formatDisplayValue", date => {
      const {
        displayValueAtRangeIndex,
        formatDisplayValue,
        range
      } = this.props; // @ts-ignore

      const formatString = this.normalizeDashes(this.props.formatString);

      if (typeof displayValueAtRangeIndex === 'number') {
        if (process.env.NODE_ENV !== "production") {
          if (!range) {
            console.error('displayValueAtRangeIndex only applies if range');
          }

          if (range && displayValueAtRangeIndex > 1) {
            console.error('displayValueAtRangeIndex value must be 0 or 1');
          }
        }

        if (date && Array.isArray(date)) {
          const value = date[displayValueAtRangeIndex];

          if (formatDisplayValue) {
            return formatDisplayValue(value, formatString);
          }

          return this.formatDate(value, formatString);
        }
      }

      if (formatDisplayValue) {
        return formatDisplayValue(date, formatString);
      }

      return this.formatDate(date, formatString);
    });

    _defineProperty(this, "open", inputRole => {
      this.setState({
        isOpen: true,
        isPseudoFocused: true,
        calendarFocused: true,
        selectedInput: inputRole
      }, this.props.onOpen);
    });

    _defineProperty(this, "close", () => {
      const isPseudoFocused = false;
      this.setState({
        isOpen: false,
        selectedInput: null,
        isPseudoFocused,
        calendarFocused: false
      }, this.props.onClose);
    });

    _defineProperty(this, "handleEsc", () => {
      if (this.state.lastActiveElm) {
        this.state.lastActiveElm.focus();
      }

      this.close();
    });

    _defineProperty(this, "handleInputBlur", () => {
      if (!this.state.isPseudoFocused) {
        this.close();
      }
    });

    _defineProperty(this, "getMask", () => {
      const {
        formatString,
        mask,
        range,
        separateRangeInputs
      } = this.props;

      if (mask === null || mask === undefined && formatString !== DEFAULT_DATE_FORMAT) {
        return null;
      }

      if (mask) {
        return this.normalizeDashes(mask);
      }

      if (range && !separateRangeInputs) {
        return `9999/99/99 ${INPUT_DELIMITER} 9999/99/99`;
      }

      return '9999/99/99';
    });

    _defineProperty(this, "handleInputChange", (event, inputRole) => {
      const inputValue = this.props.range && this.props.separateRangeInputs ? combineSeparatedInputs(event.currentTarget.value, this.state.inputValue, inputRole) : event.currentTarget.value;
      const mask = this.getMask(); // @ts-ignore

      const formatString = this.normalizeDashes(this.props.formatString);

      if (typeof mask === 'string' && inputValue === mask.replace(/9/g, ' ') || inputValue.length === 0) {
        if (this.props.range) {
          this.handleChange([]);
        } else {
          this.handleChange(null);
        }
      }

      this.setState({
        inputValue
      }); // @ts-ignore

      const parseDateString = dateString => {
        if (formatString === DEFAULT_DATE_FORMAT) {
          return this.dateHelpers.parse(dateString, 'slashDate', this.props.locale);
        }

        return this.dateHelpers.parseString(dateString, formatString, this.props.locale);
      };

      if (this.props.range && typeof this.props.displayValueAtRangeIndex !== 'number') {
        const [left, right] = this.normalizeDashes(inputValue).split(` ${INPUT_DELIMITER} `);
        let startDate = this.dateHelpers.date(left);
        let endDate = this.dateHelpers.date(right);

        if (formatString) {
          startDate = parseDateString(left);
          endDate = parseDateString(right);
        }

        const datesValid = this.dateHelpers.isValid(startDate) && this.dateHelpers.isValid(endDate); // added equal case so that times within the same day can be expressed

        const rangeValid = this.dateHelpers.isAfter(endDate, startDate) || this.dateHelpers.isEqual(startDate, endDate);

        if (datesValid && rangeValid) {
          this.handleChange([startDate, endDate]);
        }
      } else {
        const dateString = this.normalizeDashes(inputValue);
        let date = this.dateHelpers.date(dateString);
        const formatString = this.props.formatString; // Prevent early parsing of value.
        // Eg 25.12.2 will be transformed to 25.12.0002 formatted from date to string
        // @ts-ignore

        if (dateString.replace(/(\s)*/g, '').length < formatString.replace(/(\s)*/g, '').length) {
          // @ts-ignore
          date = null;
        } else {
          date = parseDateString(dateString);
        }

        const {
          displayValueAtRangeIndex,
          range,
          value
        } = this.props;

        if (date && this.dateHelpers.isValid(date)) {
          if (range && Array.isArray(value) && typeof displayValueAtRangeIndex === 'number') {
            let [left, right] = value;

            if (displayValueAtRangeIndex === 0) {
              left = date;

              if (!right) {
                this.handleChange([left]);
              } else {
                if (this.dateHelpers.isAfter(right, left) || this.dateHelpers.isEqual(left, right)) {
                  this.handleChange([left, right]);
                } else {
                  // Is resetting back to previous value appropriate? Invalid range is not
                  // communicated to the user, but if it was not reset the text value would
                  // show one value and date value another. This seems a bit better but clearly
                  // has a downside.
                  this.handleChange([...value]);
                }
              }
            } else if (displayValueAtRangeIndex === 1) {
              right = date;

              if (!left) {
                // If start value is not defined, set start/end to the same day.
                this.handleChange([right, right]);
              } else {
                if (this.dateHelpers.isAfter(right, left) || this.dateHelpers.isEqual(left, right)) {
                  this.handleChange([left, right]);
                } else {
                  // See comment above about resetting dates on invalid range
                  this.handleChange([...value]);
                }
              }
            }
          } else {
            this.handleChange(date);
          }
        }
      }
    });

    _defineProperty(this, "handleKeyDown", event => {
      if (!this.state.isOpen && event.keyCode === 40) {
        this.open();
      } else if (this.state.isOpen && (event.key === 'ArrowDown' || event.key === 'Enter')) {
        // next line prevents the page jump on the initial arrowDown
        event.preventDefault();
        this.focusCalendar();
      } else if (this.state.isOpen && event.keyCode === 9) {
        this.close();
      }
    });

    _defineProperty(this, "focusCalendar", () => {
      if (typeof document !== 'undefined') {
        const lastActiveElm = document.activeElement;
        this.setState({
          calendarFocused: true,
          lastActiveElm
        });
      }
    });

    _defineProperty(this, "normalizeDashes", inputValue => {
      // replacing both hyphens and em-dashes with en-dashes
      return inputValue.replace(/-/g, INPUT_DELIMITER).replace(/—/g, INPUT_DELIMITER);
    });

    _defineProperty(this, "generateAriaDescribedByIds", () => {
      let idList = this.state.ariaDescribedby;

      if (this.props['aria-describedby']) {
        idList = `${this.props['aria-describedby']} ${idList}`;
      }

      return idList;
    });

    _defineProperty(this, "hasLockedBehavior", () => {
      return this.props.rangedCalendarBehavior === RANGED_CALENDAR_BEHAVIOR.locked && this.props.range && this.props.separateRangeInputs;
    });

    _defineProperty(this, "getPlaceholder", () => this.props.placeholder || this.props.placeholder === '' ? this.props.placeholder : this.props.range && !this.props.separateRangeInputs ? `YYYY/MM/DD ${INPUT_DELIMITER} YYYY/MM/DD` : 'YYYY/MM/DD');

    this.dateHelpers = new DateHelpers(props.adapter);
    this.state = {
      calendarFocused: false,
      isOpen: false,
      selectedInput: null,
      isPseudoFocused: false,
      lastActiveElm: null,
      inputValue: this.formatDisplayValue(props.value) || '',
      ariaDescribedby: null // we initialize this post-mount to prevent SSR hydration issue

    };
  }

  componentDidMount() {
    this.setState({
      ariaDescribedby: uid(this)
    });
  }

  getNullDatePlaceholder(formatString) {
    return (this.getMask() || formatString).split(INPUT_DELIMITER)[0].replace(/[0-9]|[a-z]/g, ' ');
  }

  formatDate(date, formatString) {
    const format = date => {
      if (formatString === DEFAULT_DATE_FORMAT) {
        return this.dateHelpers.format(date, 'slashDate', this.props.locale);
      }

      return this.dateHelpers.formatDate(date, formatString, this.props.locale);
    };

    if (!date) {
      return '';
    } else if (Array.isArray(date) && !date[0] && !date[1]) {
      return '';
    } else if (Array.isArray(date) && !date[0] && date[1]) {
      const endDate = format(date[1]);
      const startDate = this.getNullDatePlaceholder(formatString);
      return [startDate, endDate].join(` ${INPUT_DELIMITER} `);
    } else if (Array.isArray(date)) {
      return date.map(day => day ? format(day) : '').join(` ${INPUT_DELIMITER} `);
    } else {
      return format(date);
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.value !== this.props.value) {
      this.setState({
        inputValue: this.formatDisplayValue(this.props.value)
      });
    }
  }

  renderInputComponent(locale, inputRole) {
    const {
      overrides = {}
    } = this.props;
    const [InputComponent, inputProps] = getOverrides(overrides.Input, MaskedInput);
    const inputLabel = this.props['aria-label'] || `${this.props.range ? locale.datepicker.ariaLabelRange : locale.datepicker.ariaLabel}`;
    const [startDate = '', endDate = ''] = (this.state.inputValue || '').split(` ${INPUT_DELIMITER} `);
    const value = inputRole === INPUT_ROLE.startDate ? startDate : inputRole === INPUT_ROLE.endDate ? endDate : this.state.inputValue;
    return /*#__PURE__*/React.createElement(InputComponent, _extends({
      "aria-disabled": this.props.disabled,
      "aria-label": inputLabel,
      error: this.props.error,
      positive: this.props.positive,
      "aria-describedby": this.generateAriaDescribedByIds(),
      "aria-labelledby": this.props['aria-labelledby'],
      "aria-required": this.props.required || null,
      disabled: this.props.disabled,
      size: this.props.size,
      value: value // @ts-ignore
      ,
      onChange: event => this.handleInputChange(event, inputRole),
      placeholder: this.getPlaceholder(),
      mask: this.getMask(),
      required: this.props.required,
      clearable: this.props.clearable
    }, inputProps));
  }

  renderCalendarButton() {
    const {
      overrides = {}
    } = this.props;
    const [ButtonComponent, buttonProps] = getOverrides(overrides.CalendarButton, Button);
    return /*#__PURE__*/React.createElement(ButtonComponent, _extends({
      "aria-describedby": this.props['aria-describedby'],
      "aria-labelledby": this.props['aria-labelledby'],
      startEnhancer: () => /*#__PURE__*/React.createElement(CalendarIcon, null),
      endEnhancer: () => /*#__PURE__*/React.createElement(ChevronDown, null),
      kind: KIND.tertiary,
      size: SIZE.compact,
      onClick: this.open,
      onBlur: this.handleInputBlur,
      onKeyDown: this.handleKeyDown,
      overrides: mergeOverrides({
        BaseButton: {
          style: {
            border: '3px solid',
            borderColor: colors.black
          }
        }
      }, buttonProps.overrides)
    }, buttonProps));
  }

  render() {
    const {
      overrides = {},
      startDateLabel = 'Start Date',
      endDateLabel = 'End Date'
    } = this.props;
    const [PopoverComponent, popoverProps] = getOverrides(overrides.Popover, Popover);
    const [InputWrapper, inputWrapperProps] = getOverrides(overrides.InputWrapper, StyledInputWrapper);
    const [StartDate, startDateProps] = getOverrides(overrides.StartDate, StyledStartDate);
    const [EndDate, endDateProps] = getOverrides(overrides.EndDate, StyledEndDate);
    const [InputLabel, inputLabelProps] = getOverrides(overrides.InputLabel, StyledInputLabel);
    const singleDateFormatString = this.props.formatString || DEFAULT_DATE_FORMAT;
    const formatString = this.props.range && !this.props.separateRangeInputs ? `${singleDateFormatString} ${INPUT_DELIMITER} ${singleDateFormatString}` : singleDateFormatString;
    return /*#__PURE__*/React.createElement(LocaleContext.Consumer, null, locale => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(PopoverComponent, _extends({
      accessibilityType: ACCESSIBILITY_TYPE.none,
      focusLock: false,
      autoFocus: false,
      mountNode: this.props.mountNode,
      placement: PLACEMENT.bottom,
      isOpen: this.state.isOpen,
      onClickOutside: this.close,
      onEsc: this.handleEsc,
      content: /*#__PURE__*/React.createElement(Calendar, _extends({
        adapter: this.props.adapter,
        autoFocusCalendar: this.state.calendarFocused,
        trapTabbing: true,
        value: this.props.value
      }, this.props, {
        onChange: this.onCalendarSelect,
        selectedInput: this.state.selectedInput,
        hasLockedBehavior: this.hasLockedBehavior(),
        primaryButton: {
          label: 'Done',
          onClick: this.close
        },
        secondaryButton: {
          label: 'Cancel',
          onClick: this.close
        }
      }))
    }, popoverProps), /*#__PURE__*/React.createElement("div", {
      style: {
        display: 'flex',
        flexDirection: 'row',
        gap: '8px'
      }
    }, /*#__PURE__*/React.createElement(InputWrapper, _extends({}, inputWrapperProps, {
      $separateRangeInputs: this.props.range && this.props.separateRangeInputs
    }), this.props.range && this.props.separateRangeInputs ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(StartDate, startDateProps, /*#__PURE__*/React.createElement(InputLabel, inputLabelProps, startDateLabel), this.renderInputComponent(locale, INPUT_ROLE.startDate)), /*#__PURE__*/React.createElement(EndDate, endDateProps, /*#__PURE__*/React.createElement(InputLabel, inputLabelProps, endDateLabel), this.renderInputComponent(locale, INPUT_ROLE.endDate))) : /*#__PURE__*/React.createElement(React.Fragment, null, this.renderInputComponent(locale))), this.renderCalendarButton())), /*#__PURE__*/React.createElement("p", {
      // @ts-ignore
      id: this.state.ariaDescribedby,
      style: {
        position: 'fixed',
        width: '0px',
        height: '0px',
        borderLeftWidth: 0,
        borderRightWidth: 0,
        borderTopWidth: 0,
        borderBottomWidth: 0,
        padding: 0,
        overflow: 'hidden',
        clip: 'rect(0, 0, 0, 0)',
        clipPath: 'inset(100%)'
      }
    }, getInterpolatedString(locale.datepicker.screenReaderMessageInput, {
      formatString: formatString
    })), /*#__PURE__*/React.createElement("p", {
      "aria-live": "assertive",
      style: {
        position: 'fixed',
        width: '0px',
        height: '0px',
        borderLeftWidth: 0,
        borderRightWidth: 0,
        borderTopWidth: 0,
        borderBottomWidth: 0,
        padding: 0,
        overflow: 'hidden',
        clip: 'rect(0, 0, 0, 0)',
        clipPath: 'inset(100%)'
      }
    }, // No date selected
    !this.props.value || Array.isArray(this.props.value) && !this.props.value[0] && !this.props.value[1] ? '' : // Date selected in a non-range picker
    !Array.isArray(this.props.value) ? getInterpolatedString(locale.datepicker.selectedDate, {
      date: this.state.inputValue || ''
    }) : // Start and end dates are selected in a range picker
    this.props.value[0] && this.props.value[1] ? getInterpolatedString(locale.datepicker.selectedDateRange, {
      startDate: this.formatDisplayValue(this.props.value[0]),
      endDate: this.formatDisplayValue(this.props.value[1])
    }) : // A single date selected in a range picker
    `${getInterpolatedString(locale.datepicker.selectedDate, {
      date: this.formatDisplayValue(this.props.value[0])
    })} ${locale.datepicker.selectSecondDatePrompt}`)));
  }

}

_defineProperty(Datepicker, "defaultProps", {
  'aria-describedby': null,
  // @ts-ignore
  value: null,
  formatString: DEFAULT_DATE_FORMAT,
  adapter: dateFnsAdapter
});