import clsx from 'clsx';
import { DateTime } from 'luxon';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { usePopper } from 'react-popper';
import { noop } from '../../../helpers/utils';
import { useClickOutside } from '../../../hooks';
import { Button, ButtonContext } from '../../Buttons';
import { DateTimePicker } from '../../DateTime/DateTimePicker';
import { IconName } from '../../Icons';
import { formatDate, formatDateTime } from '../../Utils';
import { BaseFormControl } from '../Form.models';
import { FormElementContainer } from '../FormElementContainer';
import classes from './DateTimeText.scss';

export interface DateTimeTextProps extends BaseFormControl {
  /** Current value the form control has */
  value?: string;
  /** Input placeholder */
  placeholder?: string;
  /** Whether or not the control should start focused (default: false) */
  autoFocus?: boolean;
  /** Whether or not the control supports auto complete */
  autoComplete?: 'on' | 'off';
  /** Whether the control modifies the time portion of the value (default: true) */
  modifyTime?: boolean;
  /** Callback when the datepicker popup is closed or the date field value is changed */
  onChange?: (value: string | null, isValidDate: boolean) => void;
}

/**
 * This component should be used to render a TextBox with a DateTime picker inside a Formik form.
 *
 * @example
 * <Field name="title" label="Title" as={SingleLineTextField} />
 */
export const DateTimeText: React.FC<DateTimeTextProps> = ({
  name,
  id,
  value = '',
  disabled = false,
  placeholder,
  error = undefined,
  autoFocus = false,
  autoComplete,
  onChange = noop,
  modifyTime = true,
  className = '',
  ...rest
}) => {
  const container = useRef<HTMLDivElement>(null);
  const pickerRef = useRef<HTMLDivElement | null>(null);
  const [pickerElement, setPickerElement] = useState<HTMLDivElement | null>(
    null,
  );
  const errorMsg: string | undefined = error;
  const [display, setDisplay] = useState<string>(() => {
    const locale = fromISODate(value, modifyTime);
    return locale ?? '';
  });

  const [showPicker, setShowPicker] = useState(false);

  // Close picker when clicking outside the picker element
  useClickOutside([pickerRef], () => setShowPicker(false), showPicker);

  const { styles, attributes } = usePopper(container.current, pickerElement, {
    placement: 'bottom-start',
    modifiers: [
      {
        name: 'flip',
        options: {
          fallbackPlacements: ['top-start', 'bottom-start'],
        },
      },
      {
        name: 'preventOverflow',
        options: {
          padding: 8,
        },
      },
    ],
  });

  useEffect(() => {
    const locale = fromISODate(value, modifyTime);
    setDisplay(locale ?? '');
  }, [modifyTime, value]);

  const onBlurHandler = useCallback(
    (e: React.FocusEvent<HTMLInputElement>) => {
      const textValue = e.target.value;
      const parsedValue = modifyTime
        ? DateTime.fromFormat(textValue, 'f')
        : DateTime.fromFormat(textValue, 'D');

      if (parsedValue.isValid) {
        onChange(parsedValue.toISO(), parsedValue.isValid);
      } else if (textValue === '') {
        onChange(null, true);
      } else {
        // Fallback parser if the user types in an ISO date
        const isoParsedValue = DateTime.fromISO(textValue);

        if (isoParsedValue.isValid) {
          onChange(isoParsedValue.toISO(), isoParsedValue.isValid);
        } else {
          onChange(textValue, parsedValue.isValid);
        }
      }
    },
    [modifyTime, onChange],
  );

  return (
    <FormElementContainer
      {...rest}
      className={clsx(classes.container, 'datetime-container', className)}
      error={errorMsg}
      dataTestFieldType="DateTime"
      htmlFor={id ?? name}
    >
      <div className={clsx(classes.inputbutton)} ref={container}>
        <input
          className={clsx({ [classes.hasError]: errorMsg })}
          id={id ?? name}
          name={name}
          type={'text'}
          value={display}
          disabled={disabled}
          placeholder={disabled ? undefined : placeholder}
          autoFocus={autoFocus}
          autoComplete={autoComplete}
          onChange={(e) => setDisplay(e.target.value)}
          onBlur={onBlurHandler}
        />
        <Button
          type="button"
          icon={IconName.Calendar}
          buttonContext={ButtonContext.Icon}
          disabled={disabled}
          className={clsx(classes.button)}
          onButtonClicked={(e) => {
            e.persist();
            e.preventDefault();
            setShowPicker(!showPicker);
          }}
        />
      </div>
      {showPicker &&
        createPortal(
          <div
            ref={(el) => {
              setPickerElement(el);
              pickerRef.current = el;
            }}
            className={clsx(classes.picker)}
            style={styles.popper}
            {...attributes.popper}
          >
            <DateTimePicker
              value={getValue(value)}
              onSelected={(value) => {
                if (!modifyTime) {
                  setShowPicker(false);
                }
                onChange && onChange(DateTime.fromJSDate(value).toISO(), true);
              }}
              showTimePicker={modifyTime}
            />
          </div>,
          document.body,
        )}
    </FormElementContainer>
  );
};

function getValue(DateString: string): Date | undefined {
  if (DateString === undefined) {
    return undefined;
  }

  const parsedValue = DateTime.fromISO(DateString);

  if (parsedValue.isValid) {
    return parsedValue.toJSDate();
  }

  return undefined;
}

function fromISODate(ISODateString: string, modifyTime: boolean): string {
  const parsedValue = DateTime.fromISO(ISODateString);
  if (parsedValue.isValid) {
    return modifyTime
      ? formatDateTime(ISODateString) ?? ''
      : formatDate(ISODateString) ?? '';
  } else {
    return ISODateString;
  }
}
