import React, {
  ReactElement,
  useState,
  useCallback,
  ChangeEvent,
  FocusEvent,
  useEffect,
} from 'react';
import formatTime from 'date-fns/fp/format';
import isValid from 'date-fns/fp/isValid';
import parse from 'date-fns/fp/parse';
import css from '../../utils/css';

import Input from '../Input';
import PanelContent from './PanelContent';
import Dropdown from '../Dropdown';
import { IconName } from '../Icon';
import { CommonProps } from '../common';

import { TimePickerWrapper } from './StyledTimePicker';

export interface TimePickerProps 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;
  /**
   * Whether the picker is disabled.
   */
  disabled?: boolean;
  /**
   * Time format. Following date-fns's format (https://date-fns.org/v2.16.1/docs/format).
   */
  format?: string;
  /**
   * Id of <input> element.
   */
  id?: string;
  /**
   * Whether the input is invalid.
   */
  invalid?: boolean;
  /**
   * Minute interval.
   */
  minuteStep?: 1 | 5 | 10 | 15 | 20 | 30 | 60;
  /**
   * Name of <input> element, is used to refer to the form data for submission.
   */
  name?: string;
  /**
   * Blur event handler.
   */
  onBlur?: (e: FocusEvent) => void;
  /**
   * onChange event handler.
   */
  onChange?: (value: string) => void;
  /**
   * Placeholder text in the absence of any value.
   */
  placeholder?: 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 time which must be in correct format.
   */
  value?: string;
  /**
   * Whether to use 12-hour system. This only works if `format` is not defined.
   */
  with12Hours?: boolean;
  /**
   * Whether to show seconds.
   */
  withSeconds?: boolean;
}

const generateFormat = (
  withSeconds: boolean,
  with12Hours: boolean,
  format?: string
): string => {
  if (format !== undefined) return format;
  if (withSeconds === true) {
    if (with12Hours === true) return 'hh:mm:ss aa';
    return 'HH:mm:ss';
  }
  if (with12Hours === true) return 'hh:mm aa';
  return 'HH:mm';
};

const TimePicker = ({
  autoComplete,
  size = 'medium',
  onChange,
  onBlur,
  invalid = false,
  placeholder,
  prefix,
  disabled = false,
  value,
  format,
  withSeconds = false,
  with12Hours = false,
  name,
  id,
  className,
  style,
  sx = {},
  'data-test-id': dataTestId,
  minuteStep = 1,
}: TimePickerProps): ReactElement => {
  const updatedFormat = generateFormat(withSeconds, with12Hours, format);
  const [pickedTime, setPickedTime] = useState<Date>();
  const [open, setOpen] = useState<boolean>(false);
  const closeDropdown = (): void => setOpen(false);

  useEffect(() => {
    // XXX: this setState logic is intentionally put here, not in OnInputChange
    // Since doing that would cause the cursor to jump to the end when setState is triggered
    if (value !== undefined) {
      const parsedChange = parse(new Date())(updatedFormat)(value);
      setPickedTime(isValid(parsedChange) ? parsedChange : undefined);
    }
  }, [updatedFormat, value]);

  const onInputChange = useCallback(
    (e: ChangeEvent<HTMLInputElement>): void => {
      const { target } = e;
      if (target.value !== undefined && onChange !== undefined) {
        onChange(target.value);
      }
    },
    [onChange]
  );

  const onSelectTime = useCallback(
    (selectedTime: Date): void => {
      if (onChange !== undefined) {
        setPickedTime(selectedTime);
        onChange(formatTime(updatedFormat, selectedTime));
      }
    },
    [onChange, updatedFormat]
  );

  const formatHas12Hours = updatedFormat.includes('hh');
  const formatHasSeconds = updatedFormat.includes('ss');

  const timePickerDropdownContent = (
    <PanelContent
      resolvedValue={pickedTime}
      with12Hours={formatHas12Hours === true}
      withSeconds={formatHasSeconds === true}
      onSelect={onSelectTime}
      minuteStep={minuteStep}
    />
  );

  const timePickerDropdownTarget = (
    <Input
      suffix="clock-outlined"
      value={value !== undefined ? value : ''}
      placeholder={placeholder !== undefined ? placeholder : updatedFormat}
      size={size}
      invalid={invalid}
      prefix={prefix}
      disabled={disabled}
      onChange={onInputChange}
      onBlur={onBlur}
      onFocus={(): void => setOpen(true)}
      id={id}
      name={name}
      autoComplete={autoComplete}
    />
  );

  return (
    <TimePickerWrapper>
      <Dropdown
        open={open}
        content={timePickerDropdownContent}
        target={timePickerDropdownTarget}
        onClose={closeDropdown}
        className={className}
        style={{ ...style, ...css(sx) }}
        data-test-id={dataTestId}
      />
    </TimePickerWrapper>
  );
};

export default TimePicker;
