import { useEffect, useState } from 'react';
import { DateTimePicker, DateTimePickerProps } from 'react-datetime-picker';
import classnames from 'classnames';
import parseISO from 'date-fns/parseISO';
import { InjectedFieldProps } from '../Field/InjectedFieldProps';
import { FormDefaults } from '../FormDefaults';
import Group, { GroupProps } from '../Group';

// Represents a single date/date string, or null
type DateTimePickerValuePiece = Date | string | null;
// Represents either a single date/date string, a pair of dates/date strings for a range, or null
type DateTimePickerValue =
  | DateTimePickerValuePiece
  | [DateTimePickerValuePiece, DateTimePickerValuePiece];

export interface DateTimePickerGroupProps<T>
  extends InjectedFieldProps<T | undefined | null>,
    Omit<
      DateTimePickerProps,
      keyof InjectedFieldProps<T> | 'name' | 'value' | 'className'
    >,
    Omit<GroupProps, keyof InjectedFieldProps<T> | 'children'> {
  convert: (date: Date) => T;
}

/**
 * Field for inputting date and time. Uses `<Group/>` and `<DateTimePicker/>`.
 *
 * Uses [react-datetime-picker](https://www.npmjs.com/package/react-datetime-picker)
 */
export default function DateTimePickerGroup<T>({
  input,
  meta,
  label,
  helpText,
  className,
  required,
  disabled,
  convert,
  maxDate = new Date(9999, 11, 31),
  minDate = new Date(1000, 0, 1),
  ...rest
}: DateTimePickerGroupProps<T>) {
  const [displayDateTime, setDisplayDateTime] = useState<Date | null>(null);

  useEffect(() => {
    const inputValue = input.value;
    if (!inputValue) {
      setDisplayDateTime(null);
    } else if (typeof inputValue === 'string') {
      const parsedDateTime = convertISODateTimeStringToDate(inputValue);
      setDisplayDateTime(parsedDateTime);
    } else if (inputValue instanceof Date) {
      setDisplayDateTime(inputValue);
    }
  }, [setDisplayDateTime, input.value]);

  return (
    <Group
      input={input}
      meta={meta}
      label={label}
      helpText={helpText}
      className={classnames(
        className,
        FormDefaults.cssClassPrefix + 'date-time-picker'
      )}
      required={required}
      disabled={disabled}>
      <div
        onBlur={() => {
          // Manually trigger Formik's onBlur when the user clicks away from the date picker
          input.onBlur();
        }}>
        <DateTimePicker
          {...rest}
          className={classnames(
            FormDefaults.cssClassPrefix + 'date-time-picker',
            className
          )}
          value={displayDateTime}
          onChange={handleChange}
          required={required}
          disabled={disabled}
          maxDate={maxDate}
          minDate={minDate}
        />
      </div>
    </Group>
  );

  function handleChange(newDateTime: DateTimePickerValue) {
    const { onChange } = input;
    if (onChange === null) {
      return;
    }

    if (!newDateTime) {
      onChange(undefined);
      setDisplayDateTime(null);
      return;
    } else if (typeof newDateTime === 'string') {
      const parsedDateTime = parseISO(newDateTime);
      setDisplayDateTime(parsedDateTime);
      onChange(convert(parsedDateTime));
    } else if (newDateTime instanceof Date) {
      setDisplayDateTime(newDateTime);
      onChange(convert(newDateTime));
    }
  }
}

function convertISODateTimeStringToDate(isoDateTimeString: string) {
  const isoDateTimeRegex =
    /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?)([+-]\d{2}:\d{2}|Z)?$/;
  const isValidIsoDateTimeString = isoDateTimeRegex.test(isoDateTimeString);
  if (isValidIsoDateTimeString) {
    return new Date(isoDateTimeString);
  }

  const errorMessage = `Invalid "date time" value of ${isoDateTimeString} provided to DateTimePickerGroup component. Please provide ISO 8601 string that includes the time zone designator. Sample strings: 2022-09-24T:08:54:12+00:00, 2022-09-24T:08:54:12Z, 2022-09-24T:08:54:12.123-05:00`;
  if (process.env.NODE_ENV !== 'production') {
    throw new Error(errorMessage);
  } else {
    console.error(errorMessage);
  }
  return null;
}
