/**
 * TEAM: frontend_infra
 * @flow
 */

import * as React from "react";
import {StyleSheet, css} from "aphrodite";
import moment from "moment-timezone";

import BaseFilter, {type PopupPlacement} from "./BaseFilter";
import DropdownList from "../select/DropdownList";

import DateTimeInput from "../date/DateTimeInput";
import {
  type CalendarDate,
  today,
  formatCalendarDate,
} from "../date/CalendarDateType";
import type {DateTimeValue} from "../date/DateTimeValueType";
import type {Size} from "../sizes";
import {momentFromCalDateWallTime, type WallTime} from "../date/wallTime";
import Group from "../Group";

import Label from "../Label";
import minDateTimeValue from "../date/minDateTimeValue";
import maxDateTimeValue from "../date/maxDateTimeValue";

export type Preset = {|
  +label: string,
  +startDate: DateTimeValue,
  +endDate: DateTimeValue,
|};

export type PresetDateFilterValue = {|
  +type: "preset",
  ...Preset,
|};

export type CustomDateFilterValue = {|
  +type: "custom",
  +startDate: DateTimeValue,
  +endDate: DateTimeValue,
|};

export type DateFilterValue = PresetDateFilterValue | CustomDateFilterValue;

const customRangeOptionLabel = "Custom date range";

export type Props = {|
  /** Description of the filter pivot, e.g. `Delivery Date Range` */
  +label: string,
  /**
   * The value for a DateRangeFilter is either a custom date range, or a preset date range.
   * The preset data range must match one of the provided presets.
   */
  +value: DateFilterValue,
  /** Called when a new date range is selected */
  +onChange: DateFilterValue => void,
  /**
   * Replaces the downOpen icon with an X button. When this button is pressed,
   * onRemove is called
   */
  +onRemove?: () => void,
  /** Whether the label hides if there are any options selected */
  +shyLabel?: boolean,
  /** Blocks calendar off prior to this date */
  +minDate?: CalendarDate,
  /** Blocks calendar off after to this date */
  +maxDate?: CalendarDate,
  /**
   * You can provide an ordered list of preset options such as "Last week"
   * that will prepopulate the DateDange list.
   */
  +presets: $ReadOnlyArray<Preset>,
  /** The size of the filter button */
  +size?: Size,
  /** Whether the filter is disabled */
  +disabled?: boolean,
  /** controls where the dropdown menu is anchored in relation to the multiselect input */
  +dropdownPlacement?: PopupPlacement,
  /** whether to use a Portal or React Fragment component */
  +noPortal?: boolean,
  /** the list of preset options to display on the time input */
  +timeInputOptions?: $ReadOnlyArray<WallTime>,
  /** displayed next to the date and time inputs. */
  +timeZone: string,
  /** event handler that gets triggered when you want to show/hide filter popup */
  +onClick?: () => void,
|};

/**
 * @short DateRangeFilter allows for filtering across date ranges
 * @category Filter
 * @brandStatus V2
 * @status Stable
 * DateRangeFilter is the filter analog for CalendarDateRange. The API is the approximately the same as CalendarDateRange, but appears in a filter UX.
 */
export default function DateTimeRangeFilter({
  label,
  value,
  onChange,
  onRemove,
  shyLabel,
  minDate,
  maxDate,
  presets,
  size,
  disabled,
  dropdownPlacement,
  noPortal = false,
  timeInputOptions,
  timeZone,
  onClick,
}: Props): React.Node {
  const startDate = {calendarDate: today(moment.tz.guess()), wallTime: null};
  const endDate = {calendarDate: today(moment.tz.guess()), wallTime: null};
  const [customDate, setCustomDate] = React.useState<CustomDateFilterValue>({
    type: "custom",
    startDate,
    endDate,
  });

  const formatDate = (dateTimeValue: DateTimeValue, timeZone: string) =>
    dateTimeValue.calendarDate && dateTimeValue.wallTime
      ? momentFromCalDateWallTime(
          dateTimeValue.calendarDate,
          dateTimeValue.wallTime,
          timeZone
        ).format("MMM D, LT")
      : formatCalendarDate(dateTimeValue.calendarDate || "", "MMM D");

  const handleCustomRangeChange = (newDateRange: CustomDateFilterValue) => {
    setCustomDate(newDateRange);
    onChange(newDateRange);
  };

  const handleOptionClick = (optionLabel: string) => {
    if (optionLabel === customRangeOptionLabel) {
      onChange(customDate);
    } else {
      const selectedPreset = presets.find(
        preset => preset.label === optionLabel
      );
      // $FlowFixMe[incompatible-call] upgrade 0.121.1 -> 0.122.0
      onChange({type: "preset", ...selectedPreset});
    }
  };

  const displayOptions = presets
    .map(preset => ({label: preset.label}))
    .concat({label: customRangeOptionLabel});

  return (
    <BaseFilter
      label={label}
      size={size}
      shyLabel={shyLabel}
      onRemove={onRemove}
      disabled={disabled}
      placement={dropdownPlacement}
      selectedText={
        value.label ||
        `${formatDate(value.startDate, timeZone)} – ${formatDate(
          value.endDate,
          timeZone
        )}`
      }
      noPortal={noPortal}
      onClick={onClick}
    >
      {closePopup => (
        <DropdownList
          highlightedOption={value.label || customRangeOptionLabel}
          onClick={label => {
            handleOptionClick(label);
            if (label !== customRangeOptionLabel) {
              closePopup();
            }
          }}
          options={displayOptions}
          footer={
            <CustomRangeFooter
              isOpen={value.type === "custom"}
              value={customDate}
              onChange={handleCustomRangeChange}
              minDate={minDate}
              maxDate={maxDate}
              timeInputOptions={timeInputOptions}
              timeZone={timeZone}
            />
          }
        />
      )}
    </BaseFilter>
  );
}

type CustomRangeFooterProps = {|
  +isOpen: boolean,
  +value: CustomDateFilterValue,
  +onChange: CustomDateFilterValue => void,
  +minDate?: CalendarDate,
  +maxDate?: CalendarDate,
  +timeInputOptions?: $ReadOnlyArray<WallTime>,
  +timeZone: string,
|};

/** A footer that allows a user to enter in a custom date range. This footer
 * appears when the `custom` option in DateRangeFilter is selected */
export function CustomRangeFooter({
  isOpen,
  value,
  onChange,
  minDate,
  maxDate,
  timeInputOptions,
  timeZone,
}: CustomRangeFooterProps): React.Element<"div"> {
  const handleChange = (newDate: DateTimeValue | null, isStart: boolean) => {
    let date = newDate;
    if (date == null || date.calendarDate == null) {
      date = {calendarDate: today(moment.tz.guess()), wallTime: null};
    }

    if (isStart) {
      onChange({
        type: "custom",
        startDate: date,
        endDate: maxDateTimeValue(date, value.endDate),
      });
    } else {
      onChange({
        type: "custom",
        startDate: minDateTimeValue(date, value.startDate),
        endDate: date,
      });
    }
  };

  if (!isOpen) {
    return <div className={css(styles.footer)} />;
  }

  return (
    <div className={css(styles.footer, styles.customRangeFooter)}>
      <Group flexDirection="column">
        <Label value="From">
          <DateTimeInput
            value={value.startDate}
            onChange={date => {
              handleChange(date, true);
            }}
            minDate={minDate}
            maxDate={maxDate}
            timeInputOptions={timeInputOptions}
            timeZone={timeZone}
          />
        </Label>
        <Label value="To">
          <DateTimeInput
            value={value.endDate}
            onChange={date => {
              handleChange(date, false);
            }}
            minDate={minDate}
            maxDate={maxDate}
            timeInputOptions={timeInputOptions}
            timeZone={timeZone}
          />
        </Label>
      </Group>
    </div>
  );
}

const dropdownWidth = 400;

const styles = StyleSheet.create({
  footer: {
    width: dropdownWidth,
  },
  customRangeFooter: {
    display: "flex",
    flexDirection: "row",
    padding: 12,
    paddingBottom: 20,
  },
});
