import clsx from 'clsx';
import { DateTime } from 'luxon';
import React, {
  KeyboardEventHandler,
  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/Transformers';
import { FilterValidationResult, FilterValue } from '../../Filters.model';
import classes from './DateTimeFilter.scss';

export interface DateTimeFilterProps {
  value?: string;

  /** Whether modifying time should be allowed */
  modifyTime: boolean;

  /** Callback triggered when a new filter value is selected */
  onSelect: (value: FilterValue) => void;

  /** Callback triggered when validation fails */
  onError?: () => void;

  /** Callback triggered for custom validations of the filter */
  onValidate?: (currentValue: FilterValue) => FilterValidationResult;

  /** CSS Class name for additional styles */
  className?: string;

  /** Optional id for the input field */
  inputId?: string;
}

export const DateTimeFilter: React.FC<DateTimeFilterProps> = ({
  onSelect,
  modifyTime,
  value: initialValue,
  onError = noop,
  onValidate: customValidate,
  className = '',
  inputId,
}) => {
  const container = useRef<HTMLDivElement>(null);
  const pickerRef = useRef<HTMLDivElement | null>(null);
  const [pickerElement, setPickerElement] = useState<HTMLDivElement | null>(
    null,
  );
  const [showPicker, setShowPicker] = useState(false);
  const [value, setValue] = useState<string>('');
  const [errorMsg, setErrorMsg] = useState<string>();
  const ENTER_KEY = 'Enter';

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

  const validateAndSet = (parsedValue: string): void => {
    const validationMessage = customValidate && customValidate(parsedValue);

    if (validationMessage) {
      setErrorMsg(validationMessage);
      onError();
    } else {
      onSelect(parsedValue);
    }
  };

  // Close picker when clicking outside with validation
  useClickOutside(
    [pickerRef],
    () => {
      setShowPicker(false);

      if (modifyTime && value?.length > 0) {
        const parsedValue = DateTime.fromFormat(value, 'f');

        if (parsedValue.isValid) {
          validateAndSet(parsedValue.toISO());
        } else {
          setErrorMsg('Date format is not valid');
          onError();
        }
      }
    },
    showPicker,
  );
  useEffect(() => {
    if (initialValue) {
      setValue(
        modifyTime
          ? formatDateTime(initialValue) ?? ''
          : formatDate(initialValue) ?? '',
      );
    }
  }, [initialValue, modifyTime]);

  const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
    if (e.key === ENTER_KEY) {
      if (e.currentTarget.value === '') {
        onSelect(undefined);
        return;
      }

      const parsedValue = modifyTime
        ? DateTime.fromFormat(value, 'f')
        : DateTime.fromFormat(value, 'D');
      if (parsedValue.isValid) {
        validateAndSet(parsedValue.toISO());
      } else {
        // Fallback parser if the user types in an ISO date
        const isoParsedValue = DateTime.fromISO(value);

        if (isoParsedValue.isValid) {
          validateAndSet(isoParsedValue.toISO());
        } else {
          setErrorMsg('Date format is not valid');
          onError();
        }
      }
    }
  };

  return (
    <>
      <div
        className={clsx(
          classes.container,
          'datetime-filter-container',
          className,
        )}
      >
        <div className={clsx(classes.dateTimeInputWrapper)}>
          <div
            className={clsx(
              classes.dateTimeInput,
              'datetime-border-wrapper',
              errorMsg && classes.hasError,
            )}
            ref={container}
          >
            <input
              id={inputId}
              autoFocus
              className={clsx(classes.inputValue)}
              onKeyDown={handleKeyDown}
              onChange={(e) => setValue(e.target.value)}
              value={value}
            />
            <Button
              className={clsx(classes.button)}
              buttonContext={ButtonContext.None}
              icon={IconName.Calendar}
              onButtonClicked={(e) => {
                e.persist();
                e.preventDefault();
                setShowPicker(!showPicker);
              }}
            />
          </div>
        </div>
        {errorMsg !== undefined && <small>{errorMsg}</small>}
      </div>
      {showPicker &&
        createPortal(
          <div
            ref={(el) => {
              setPickerElement(el);
              pickerRef.current = el;
            }}
            className={clsx(classes.picker)}
            style={styles.popper}
            {...attributes.popper}
          >
            <DateTimePicker
              value={
                initialValue
                  ? DateTime.fromISO(initialValue).toJSDate()
                  : undefined
              }
              onSelected={(value) => {
                if (modifyTime) {
                  setValue(formatDateTime(value.toISOString()) ?? '');
                } else {
                  setShowPicker(false);
                  const parsedValue = DateTime.fromJSDate(value);
                  setValue(parsedValue.toLocaleString());
                  validateAndSet(parsedValue.toISO());
                }
              }}
              showTimePicker={modifyTime}
            />
          </div>,
          document.body,
        )}
    </>
  );
};
