import React, {
  ReactElement,
  useState,
  useRef,
  useCallback,
  useEffect,
  useMemo,
  FocusEvent,
} from 'react';
import formatDate from 'date-fns/fp/format';
import parse from 'date-fns/fp/parse';
import isAfter from 'date-fns/fp/isAfter';
import isBefore from 'date-fns/fp/isBefore';
import css from '../../utils/css';

import Input from '../Input';
import Dropdown from '../Dropdown';
import Calendar from './Calendar';
import Button from '../Button';
import { IconName } from '../Icon';
import {
  DatePickerContainer,
  DateRangeInputWrapper,
  DateSeparator,
  FocusBar,
} from './StyledDatePicker';
import { focusInput, generateFocusBarStyle } from './utils';
import { fromUndefinedable, map, getOrElse } from '../../fp/Option';
import { pipe } from '../../fp/function';
import { useHover } from '../../utils/hooks';

import { CommonProps } from '../common';

export interface DateRangePickerProps extends Omit<CommonProps, 'id'> {
  /**
   * Specify the [automated assistance](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) in filling out form field values by the browser.
   */
  autoComplete?: string;
  /**
   * Allow to clear value after selected dates.
   */
  clearable?: boolean;
  /**
   * Whether the picker is disabled.
   */
  disabled?: boolean;
  /**
   * Date format. Following date-fns's format (https://date-fns.org/v2.16.1/docs/format).
   */
  format?: string;
  /**
   * Ids of <input> element.
   */
  id?: { endDate?: string; startDate?: string };
  /**
   * Whether the input is invalid.
   */
  invalid?: boolean;
  /**
   * The latest date user can select.
   */
  maxDate?: Date;
  /**
   * The earliest date user can select.
   */
  minDate?: Date;
  /**
   * Names of <input> element, is used to refer to the form data for submission.
   */
  name?: { endDate?: string; startDate?: string };
  /**
   * onBlur event handler.
   */
  onBlur?: (e: FocusEvent) => void;
  /**
   * onChange event handler.
   */
  onChange?: ({
    startDate,
    endDate,
  }: {
    endDate?: string;
    startDate?: string;
  }) => void;
  /**
   * Placeholder text in the absence of any value.
   */
  placeholder?: { endDate: string; startDate: string };
  /**
   * Name of Icon or an Icon element to render on the left side of the input, before the user's cursor.
   */
  prefix?: IconName | ReactElement;
  /**
   * The size of the input box.
   */
  size?: 'small' | 'medium' | 'large';
  /**
   * Current selected date which must be in correct format. If value is invalid, it will be skipped.
   */
  value?: { endDate?: string; startDate?: string };
}

const RemoveBtnIcon = ({
  onChange,
}: {
  onChange?: DateRangePickerProps['onChange'];
}) => (
  <Button.Icon
    data-test-id="remove-icon"
    icon="circle-cancel"
    intent="subdued-text"
    onClick={() => {
      onChange?.({ startDate: undefined, endDate: undefined });
    }}
    style={{ fontSize: 'inherit' }}
  />
);

const DateRangePicker = ({
  autoComplete,
  value,
  onBlur,
  onChange,
  minDate,
  maxDate,
  size = 'medium',
  invalid = false,
  placeholder,
  prefix,
  disabled = false,
  clearable = true,
  format = 'dd/MM/yyyy',
  name,
  id,
  className,
  style,
  sx = {},
  'data-test-id': dataTestId,
}: DateRangePickerProps): ReactElement => {
  const [focusingField, setFocusingField] = useState<
    'startDate' | 'endDate' | 'none'
  >('none');
  const [dateClickCount, setDateClickCount] = useState<number>(0);

  const startDate =
    value !== undefined && value.startDate !== undefined
      ? parse(new Date(), format, value.startDate)
      : undefined;
  const endDate =
    value !== undefined && value.endDate !== undefined
      ? parse(new Date(), format, value.endDate)
      : undefined;

  const startDateInputRef = useRef(null);
  const endDateInputRef = useRef(null);
  const wrapperRef = useRef(null);
  const isHoveringWrapper = useHover(wrapperRef);

  const removeShown =
    clearable === true &&
    isHoveringWrapper &&
    (value?.startDate !== undefined || value?.endDate !== undefined);

  const closeCalendar = useCallback(() => {
    setFocusingField('none');
  }, [setFocusingField]);

  const onFocusStartDateInput = useCallback(() => {
    setFocusingField('startDate');
  }, [setFocusingField]);

  const onFocusEndDateInput = useCallback(() => {
    setFocusingField('endDate');
  }, [setFocusingField]);

  const resetDateClickCount = useCallback(() => {
    setDateClickCount(0);
  }, [setDateClickCount]);

  const onSelectDate = useCallback(
    (date: Date): void => {
      if (onChange === undefined) return;

      switch (focusingField) {
        case 'startDate':
          if (endDate === undefined || isAfter(endDate, date)) {
            onChange({
              startDate: formatDate(format, date),
              endDate: undefined,
            });
            focusInput(endDateInputRef.current);
          } else {
            onChange({
              startDate: formatDate(format, date),
              endDate: formatDate(format, endDate),
            });
            if (dateClickCount + 1 < 2) focusInput(endDateInputRef.current);
          }
          break;
        case 'endDate':
          if (startDate === undefined || isBefore(startDate, date)) {
            onChange({
              startDate: undefined,
              endDate: formatDate(format, date),
            });
            focusInput(startDateInputRef.current);
          } else {
            onChange({
              startDate: formatDate(format, startDate),
              endDate: formatDate(format, date),
            });
            if (dateClickCount + 1 < 2) focusInput(startDateInputRef.current);
          }
          break;
      }

      setDateClickCount(dateClickCount + 1);
    },
    [
      focusingField,
      startDate,
      endDate,
      dateClickCount,
      setDateClickCount,
      format,
      onChange,
    ]
  );

  useEffect(() => {
    if (dateClickCount === 2) {
      closeCalendar();
    }
  }, [dateClickCount, closeCalendar]);

  const maybeId = fromUndefinedable(id);
  const maybeName = fromUndefinedable(name);
  const maybeStartDate = fromUndefinedable(startDate);
  const maybeEndDate = fromUndefinedable(endDate);
  const maybePlaceholder = fromUndefinedable(placeholder);
  const focusBarStyle = useMemo(() => {
    switch (focusingField) {
      case 'none':
        return {};
      case 'startDate':
        return generateFocusBarStyle(startDateInputRef.current);
      case 'endDate':
        return generateFocusBarStyle(endDateInputRef.current);
    }
  }, [focusingField]);
  const isCalendarOpening = useMemo(() => {
    switch (focusingField) {
      case 'none':
        return false;
      case 'startDate':
        return true;
      case 'endDate':
        return true;
    }
  }, [focusingField]);

  const dateInputs = (
    <DateRangeInputWrapper
      className={className}
      style={{ ...style, ...css(sx) }}
      data-test-id={dataTestId}
      ref={wrapperRef}
    >
      <Input
        prefix={prefix}
        suffix={<DateSeparator>-</DateSeparator>}
        onFocus={onFocusStartDateInput}
        onBlur={onBlur}
        onClick={resetDateClickCount}
        size={size}
        invalid={invalid}
        disabled={disabled}
        ref={startDateInputRef}
        readonly
        placeholder={pipe(
          maybePlaceholder,
          map(obj => obj.startDate),
          getOrElse(() => format)
        )}
        value={pipe(
          maybeStartDate,
          map(formatDate(format)),
          getOrElse(() => '')
        )}
        id={pipe(
          maybeId,
          map(obj => obj.startDate),
          getOrElse(() => undefined)
        )}
        name={pipe(
          maybeName,
          map(obj => obj.startDate),
          getOrElse(() => undefined)
        )}
        autoComplete={autoComplete}
      />
      <Input
        suffix={
          removeShown ? <RemoveBtnIcon onChange={onChange} /> : 'calendar'
        }
        onFocus={onFocusEndDateInput}
        onBlur={onBlur}
        onClick={resetDateClickCount}
        size={size}
        invalid={invalid}
        disabled={disabled}
        ref={endDateInputRef}
        readonly
        placeholder={pipe(
          maybePlaceholder,
          map(obj => obj.endDate),
          getOrElse(() => format)
        )}
        value={pipe(
          maybeEndDate,
          map(formatDate(format)),
          getOrElse(() => '')
        )}
        id={pipe(
          maybeId,
          map(obj => obj.endDate),
          getOrElse(() => undefined)
        )}
        name={pipe(
          maybeName,
          map(obj => obj.endDate),
          getOrElse(() => undefined)
        )}
        autoComplete={autoComplete}
      />
      <FocusBar style={focusBarStyle} />
    </DateRangeInputWrapper>
  );

  const calendar = (
    <Calendar.Double
      startDate={startDate}
      endDate={endDate}
      minDate={minDate}
      maxDate={maxDate}
      onSelectDate={onSelectDate}
      isSettingStartDate={focusingField === 'startDate'}
      isSettingEndDate={focusingField === 'endDate'}
      dateClickCount={dateClickCount}
    />
  );

  return (
    <DatePickerContainer>
      <Dropdown
        target={dateInputs}
        content={calendar}
        open={isCalendarOpening}
        onClose={closeCalendar}
        style={{ minWidth: 'unset' }}
      />
    </DatePickerContainer>
  );
};

export default DateRangePicker;
