import React, { useState, useEffect, useCallback, useRef } from "react";
import classNames from "classnames";
import moment from "moment";
import { ControlledProps, useDefaultValue } from "../form/controlled";
import CalendarPart from "../calendar/CalendarPart";
import { CalendarTable } from "../calendar/CalendarTable";
import { Input } from "../input/Input";
import { useTranslation } from "../i18n";
import {
  genShowHourMinuteSecond,
  getValidTimeValue,
  isValidTimeValue,
} from "./util";
import { TimeDisabledProps, TimePickerProps } from "./TimeProps";
import { DropdownBox } from "../dropdown";
import { RangeDateType } from "../calendar/DateProps";
import { CommonDatePickerProps } from "../datepicker";
import { Button } from "../button";
import { Popover } from "../popover/Popover";
import { DatePickerTrigger, isAfter } from "../datepicker/util";
import { useDefault } from "../_util/use-default";
import { useConfig } from "../_util/config-context";
import { forwardRefWithStatics } from "../_util/forward-ref-with-statics";
import { mergeEventProps } from "../_util/merge-event-props";
import { KeyMap } from "../_util/key-map";
import { Icon } from "../icon";

export interface TimeRangePickerProps
  extends CommonDatePickerProps,
    ControlledProps<RangeDateType>,
    Pick<TimePickerProps, "hourStep" | "minuteStep" | "secondStep"> {
  /**
   * 是否禁用选择值自动顺序校正，使得可以选择跨天类型的时间
   *
   * 如 22:00 ~ 00:00，23:00 ~ 01:00, 10:00 ~ 08:00 等
   *
   * @default false
   */
  disableAutoAdjust?: boolean;

  /**
   * 分隔符
   * @default ~
   */
  separator?: string;

  /**
   * placeholder
   * @default “选择时间”
   */
  placeholder?: string;

  /**
   * 日期展示格式
   * @default "HH:mm:ss"
   */
  format?: string;

  /**
   * 不可选的时间
   */
  disabledTime?: (
    dates: RangeDateType,
    partial: "start" | "end"
  ) => TimeDisabledProps;

  /**
   * 标题渲染
   * @default ["开始时间","结束时间"]
   * @since 2.0.14
   */
  caption?: [React.ReactNode, React.ReactNode];
}

const getDefaultMoment = ({
  range,
  timeDisabled,
  hourStep = 1,
  minuteStep = 1,
  secondStep = 1,
}: Partial<TimeRangePickerProps> & { timeDisabled: TimeDisabledProps }) => {
  return getValidTimeValue(moment("00:00:00", "HH:mm:ss"), {
    range,
    hourStep,
    minuteStep,
    secondStep,
    ...timeDisabled,
  });
};

function isValidRangeMomentValue(value: any) {
  return (
    Array.isArray(value) &&
    moment.isMoment(value[0]) &&
    moment.isMoment(value[1])
  );
}

export const TimeRangePicker = forwardRefWithStatics(
  function TimeRangePicker(
    props: TimeRangePickerProps,
    ref: React.Ref<HTMLDivElement>
  ) {
    const { classPrefix } = useConfig();
    const t = useTranslation(moment);

    const {
      className,
      style,
      header,
      value,
      onChange,
      disabled,
      separator = "~",
      format = "HH:mm:ss",
      placeholder = t.selectTime,
      defaultOpen = false,
      open,
      onOpenChange = () => null,
      placement = "bottom-start",
      placementOffset = 5,
      closeOnScroll = true,
      escapeWithReference,
      popupContainer,
      overlayClassName,
      overlayStyle,
      range,
      clearable,
      disabledTime = () => ({}),
      hourStep = 1,
      minuteStep = 1,
      secondStep = 1,
      disableAutoAdjust,
      caption = [t.startTime, t.endTime],
    } = useDefaultValue(props, [null, null]);

    const { showMilliseconds } = genShowHourMinuteSecond(format);

    // 当前选中时间
    const [curValue, setCurValue] = useState<RangeDateType>(
      isValidRangeMomentValue(value)
        ? [value[0].clone(), value[1].clone()]
        : [null, null]
    );

    const [hover, setHover] = useState<boolean>(false);

    // 选择器是否展开
    const [active, setActive] = useDefault(open, defaultOpen, onOpenChange);

    // 输入框显示值
    const getInputValue = useCallback(
      (value: RangeDateType): string => {
        const [start, end] = value || [null, null];
        if (moment.isMoment(start) && moment.isMoment(end)) {
          return `${start
            .locale(t.locale)
            .format(format)} ${separator} ${end
            .locale(t.locale)
            .format(format)}`;
        }
        return "";
      },
      [format, separator, t.locale]
    );
    const [inputValue, setInputValue] = useState<string>(
      getInputValue(curValue)
    );

    useEffect(() => {
      const [start, end] = value;
      const curStart = moment.isMoment(start)
        ? start.clone()
        : getDefaultMoment({
            range,
            hourStep,
            minuteStep,
            secondStep,
            timeDisabled: disabledTime(value, "start"),
          });
      const curEnd = moment.isMoment(end)
        ? end.clone()
        : getDefaultMoment({
            range,
            hourStep,
            minuteStep,
            secondStep,
            timeDisabled: disabledTime([curStart, end], "end"),
          });
      setCurValue([curStart, curEnd]);
      setInputValue(getInputValue(value));
    }, [format, separator, value]); // eslint-disable-line react-hooks/exhaustive-deps

    const timerRef = useRef(null);
    useEffect(
      () => () => {
        clearTimeout(timerRef.current);
      },
      []
    );

    function handleInputChange(content: string) {
      setInputValue(content);
      // 格式校验
      const [startStr, endStr] = content.split(separator).map(i => i.trim());
      const value: RangeDateType = [
        moment(startStr, format, true),
        moment(endStr, format, true),
      ];

      if (!value[0].isValid() || !value[1].isValid()) {
        return;
      }

      const startValid = isValidTimeValue(value[0], {
        format,
        hourStep,
        minuteStep,
        secondStep,
        range,
        ...disabledTime(value, "start"),
      });

      const endValid = isValidTimeValue(value[1], {
        format,
        hourStep,
        minuteStep,
        secondStep,
        range,
        ...disabledTime(value, "end"),
      });
      if (startValid && endValid) {
        setCurValue(value);
      }
    }

    function handleKeyDown(event: React.KeyboardEvent) {
      if (!active) {
        handleOpen();
        return;
      }
      switch (event.key) {
        case KeyMap.Enter:
          handleOk(event);
          break;
        case KeyMap.Esc:
          handleClose();
          break;
      }
    }

    function handleChange(
      partial: "start" | "end",
      value: RangeDateType
      // context: DateChangeContext
    ): void {
      const [start, end] = value;
      const fullValue: RangeDateType = [
        start ||
          getDefaultMoment({
            range,
            hourStep,
            minuteStep,
            secondStep,
            timeDisabled: disabledTime(value, "start"),
          }),
        end ||
          getDefaultMoment({
            range,
            hourStep,
            minuteStep,
            secondStep,
            timeDisabled: disabledTime(value, "end"),
          }),
      ];
      // 调整至合法时间
      if (partial === "start") {
        fullValue[1] = getValidTimeValue(fullValue[1], {
          range,
          hourStep,
          minuteStep,
          secondStep,
          ...disabledTime(fullValue, "end"),
        });
      } else {
        fullValue[0] = getValidTimeValue(fullValue[0], {
          range,
          hourStep,
          minuteStep,
          secondStep,
          ...disabledTime(fullValue, "start"),
        });
      }
      setCurValue(fullValue);
      // [TODO] moment 更改后直接获取值（format）可能拿到是之前值？
      timerRef.current = setTimeout(
        () => setInputValue(getInputValue(fullValue)),
        0
      );
    }

    function handleOk(event): void {
      let value = curValue;
      if (
        !disableAutoAdjust &&
        isValidRangeMomentValue(curValue) &&
        isAfter(curValue[0], curValue[1])
      ) {
        value = [curValue[1], curValue[0]];
        setCurValue(value);
      }
      onChange(value, { event });
      handleClose();
    }

    function handleOpen(): void {
      if (disabled) {
        return;
      }
      setActive(true);
    }

    function handleClose(): void {
      setInputValue(getInputValue(value));
      setActive(false);
    }

    const timeProps = {
      hourStep,
      minuteStep,
      secondStep,
      format,
    };

    return (
      <Popover
        trigger={[
          DatePickerTrigger,
          { onOpen: handleOpen, onClose: handleClose },
        ]}
        visible={active}
        onVisibleChange={visible => {
          if (!visible) {
            handleClose();
          } else {
            setActive(visible);
          }
        }}
        placement={placement}
        placementOffset={placementOffset}
        closeOnScroll={closeOnScroll}
        escapeWithReference={escapeWithReference}
        popupContainer={popupContainer}
        overlayClassName={overlayClassName}
        overlayStyle={overlayStyle}
        overlay={
          <DropdownBox preventMouseDown>
            <CalendarPart.Panel rangeMode timeMode>
              {!!header && <CalendarPart.Header>{header}</CalendarPart.Header>}
              <CalendarPart.Body>
                <CalendarTable
                  {...props}
                  rangeType="start"
                  type="time"
                  showTime={{
                    ...timeProps,
                    caption: Array.isArray(caption) ? caption[0] : null,
                  }}
                  value={curValue}
                  onChange={value =>
                    handleChange("start", value as RangeDateType)
                  }
                />
                <CalendarTable
                  {...props}
                  rangeType="end"
                  type="time"
                  showTime={{
                    ...timeProps,
                    caption: Array.isArray(caption) ? caption[1] : null,
                  }}
                  value={curValue}
                  onChange={value =>
                    handleChange("end", value as RangeDateType)
                  }
                />
              </CalendarPart.Body>
              <CalendarPart.Footer
                right={
                  <>
                    <Button type="weak" htmlType="button" onClick={handleClose}>
                      {t.cancelText}
                    </Button>
                    <Button type="primary" htmlType="button" onClick={handleOk}>
                      {t.okText}
                    </Button>
                  </>
                }
              />
            </CalendarPart.Panel>
          </DropdownBox>
        }
      >
        <div
          ref={ref}
          className={classNames(`${classPrefix}-timepicker`, className)}
          style={style}
          {...mergeEventProps(props)}
        >
          <div
            className={classNames(`${classPrefix}-timepicker__input size-l`, {
              "is-hover": hover && value && clearable,
            })}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
          >
            <Input
              maxLength={showMilliseconds ? 27 : 19}
              disabled={disabled}
              placeholder={placeholder}
              value={inputValue}
              onChange={handleInputChange}
              onKeyDown={handleKeyDown}
              onClick={(event: React.MouseEvent) => {
                if (active) {
                  event.stopPropagation();
                }
              }}
            />
            {clearable && !disabled && (
              <Icon
                type="dismiss"
                onClick={event => {
                  event.stopPropagation();
                  onChange([null, null], { event });
                }}
              />
            )}
          </div>
        </div>
      </Popover>
    );
  },
  { defaultLabelAlign: "middle" }
);

TimeRangePicker.displayName = "TimeRangePicker";
