import React, { useState, useEffect, useRef, useCallback } from "react";
import classNames from "classnames";
import moment, { Moment } from "moment";
import { ControlledProps, useDefaultValue } from "../form/controlled";
import CalendarPart from "../calendar/CalendarPart";
import { CalendarTable, getTimeRange } from "../calendar/CalendarTable";
import { CalendarWrapper } from "./CalendarWrapper";
import { Input } from "../input/Input";
import { useTranslation } from "../i18n";
import { CommonDatePickerProps } from "./DatePickerProps";
import {
  getYearMonthDate,
  DatePickerTrigger,
  isSame,
  isAfter,
  isBefore,
  isValidDate,
  isValidMonth,
} from "./util";
import {
  genShowHourMinuteSecond,
  getHourMinuteSecond,
  getValidTimeValue,
  isValidTimeValue,
} from "../timepicker/util";
import { TimeDisabledProps } from "../timepicker/TimeProps";
import { DropdownBox } from "../dropdown";
import {
  RangeDateType,
  showTimeType,
  CalendarTableType,
  DateChangeContext,
} from "../calendar/DateProps";
import { useDefault } from "../_util/use-default";
import { Popover } from "../popover/Popover";
import { useConfig } from "../_util/config-context";
import { Icon } from "../icon";
import { noop } from "../_util/noop";
import { RangePickerContext } from "./RangePickerContext";
import { forwardRefWithStatics } from "../_util/forward-ref-with-statics";
import { mergeEventProps } from "../_util/merge-event-props";
import { KeyMap } from "../_util/key-map";

export interface RangePickerProps
  extends Omit<CommonDatePickerProps, "placeholder">,
    ControlledProps<RangeDateType> {
  /**
   * 分隔符
   * @default ~
   */
  separator?: string;

  /**
   * placeholder
   * @default "选择日期" 或 “选择时间”（包含时间选择时）
   * @snice 2.7.7
   */
  placeholder?: string | [string, string];

  /**
   * 是否开启时间选择，可传递对象设定时间选择配置
   */
  showTime?: showTimeType<RangeDateType>;

  /**
   * 不可选的日期
   */
  disabledDate?: (date: Moment, startDate?: Moment) => boolean;

  /**
   * 不可选的月份
   */
  disabledMonth?: (date: Moment, startDate?: Moment) => boolean;

  /**
   * 不可选的季度
   */
  disabledQuarter?: (date: Moment, startDate?: Moment) => boolean;

  /**
   * 日历类型
   * @default date
   */
  calendarType?: "date" | "month" | "quarter";

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

interface RangeInputValue {
  inputValueStart: string;
  separator: string;
  inputValueEnd: string;
}

type InputValueType = RangeInputValue | string;

const getFormat = (showTime, calendarType) => {
  if (calendarType === "date") {
    return showTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD";
  }

  if (calendarType === "month") {
    return "YYYY-MM";
  }

  return "YYYY";
};

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

export const RangePicker = forwardRefWithStatics(
  function RangePicker(
    props: RangePickerProps,
    ref: React.Ref<HTMLDivElement>
  ) {
    const t = useTranslation(moment);
    const { classPrefix } = useConfig();
    const {
      header,
      className,
      style,
      value,
      onChange,
      showTime,
      disabled,
      separator = "~",
      format: _format,
      placeholder = showTime ? t.selectTime : t.selectDate,
      defaultOpen = false,
      open,
      onOpenChange = noop,
      onInputValueChange = noop,
      placement = "bottom-start",
      placementOffset = 5,
      closeOnScroll = true,
      escapeWithReference,
      popupContainer,
      overlayClassName,
      overlayStyle,
      clearable,
      calendarType = "date",
      ...restProps
    } = useDefaultValue(props, [null, null]);

    const parentUnit = ["month", "quarter"].includes(calendarType)
      ? "year"
      : "month";

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

    const format = _format || getFormat(showTime, calendarType);

    // 当前面板类型
    const [type, setType] = useState<[CalendarTableType, CalendarTableType]>([
      calendarType,
      calendarType,
    ]);

    // 当前左/右面板展示时间
    const [curStartView, setStartCurView] = useState<Moment>(
      getDefaultViewMoment(0)
    );
    const [curEndView, setEndCurView] = useState<Moment>(
      getDefaultViewMoment(1)
    );

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

    // 当前 hover 的日期
    const [hovered, setHovered] = useState<Moment>(null);

    // 上次选中日期
    const preValidValueRef = useRef<RangeDateType>(value || [null, null]);

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

    /** 是否显示毫秒 */
    const { showMilliseconds } = genShowHourMinuteSecond(format);

    // 输入框显示值
    const startInputRef = useRef<HTMLInputElement>(null);
    const endInputRef = useRef<HTMLInputElement>(null);
    // 2.7.4 后改为两个输入框显示
    const getInputValue = useCallback(
      (value: RangeDateType): InputValueType => {
        const [start, end] = value || [null, null];
        if (moment.isMoment(start) && moment.isMoment(end)) {
          const startMoment = start.locale(t.locale);
          const endMoment = end.locale(t.locale);

          if (calendarType === "quarter") {
            return {
              inputValueStart: `${startMoment.format(
                "YYYY"
              )}-Q${startMoment.quarter()}`,
              separator,
              inputValueEnd: `${endMoment.format(
                "YYYY"
              )}-Q${endMoment.quarter()}`,
            };
          }

          return {
            inputValueStart: startMoment.format(format),
            separator,
            inputValueEnd: endMoment.format(format),
          };
        }
        return "";
      },
      [calendarType, format, separator, t.locale]
    );
    const [inputValue, setInputValue] = useState<InputValueType>(
      getInputValue(curValue)
    );

    useEffect(() => {
      setCurValue(
        isValidRangeValue(value)
          ? [value[0].clone(), value[1].clone()]
          : [null, null]
      );
      // [TODO] moment 更改后直接获取值（format）可能拿到是之前值？
      const timer = setTimeout(() => setInputValue(getInputValue(value)), 0);
      return () => {
        clearTimeout(timer);
      };
    }, [format, separator, value, getInputValue, active]);

    useEffect(() => {
      preValidValueRef.current = value || [null, null];
    }, [value]);

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

    useEffect(() => {
      // 同一个月不跳转面板 | 同一个年不跳转面板
      const limit = calendarType === "date" ? "M" : "y";
      if (!curValue[0]?.isSame(curValue[1], limit) && curValue[1]) {
        setStartCurView(curValue[0]);
      }
      if (!curValue[1]?.isSame(curValue[0], limit) && curValue[1]) {
        setEndCurView(curValue[1]);
      }
    }, [calendarType, curValue]);

    function handleChange(
      value: RangeDateType,
      context: DateChangeContext
    ): void {
      // 同步日期/时间
      if (showTime && isValidRangeValue(value)) {
        value = syncDate(value, context.type); // eslint-disable-line no-param-reassign
      }

      setCurValue(value);
      // [TODO] moment 更改后直接获取值（format）可能拿到是之前值？
      timerRef.current = setTimeout(
        () => setInputValue(getInputValue(value)),
        0
      );

      // 没有时间选择时没有二次确认选中
      if (!showTime && isValidRangeValue(value)) {
        onChange(value, context);
        handleClose();
      }
    }

    function handleOk(event): void {
      let value = curValue;
      if (isValidRangeValue(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);
      setType([calendarType, calendarType]);
    }

    function handleClose(): void {
      setActive(false);
    }

    /**
     * 同步日期/时间
     * 如果当前修改为日期，则同步上次时间并修正；
     * 如果当前修改为时间，则同步上次日期；
     */
    function syncDate(
      value: RangeDateType,
      type: CalendarTableType
    ): RangeDateType {
      const preValidValue = preValidValueRef.current;
      const { range, disabledTime = () => ({}) } = props;

      // 如果包含上次选择，则以上次选择为基准同步
      if (isValidRangeValue(preValidValue)) {
        if (type === "date") {
          const timeStart = getValidTimeValue(preValidValue[0], {
            range: getTimeRange(value[0], range),
            ...disabledTime(value, "start"),
            hourStep: showTime?.hourStep || 1,
            minuteStep: showTime?.minuteStep || 1,
            secondStep: showTime?.secondStep || 1,
          });
          const timeEnd = getValidTimeValue(preValidValue[1], {
            range: getTimeRange(value[1], range),
            ...disabledTime(value, "end"),
            hourStep: showTime?.hourStep || 1,
            minuteStep: showTime?.minuteStep || 1,
            secondStep: showTime?.secondStep || 1,
          });
          value[0].set(getHourMinuteSecond(timeStart));
          value[1].set(getHourMinuteSecond(timeEnd));
        }
        if (type === "time") {
          value[0].set(getYearMonthDate(preValidValue[0]));
          value[1].set(getYearMonthDate(preValidValue[1]));
        }
        // 如果是首次选择，则将当前时间进行修正
      } else if (type === "date") {
        // 合并 showTime.defaultValue
        if (isValidRangeValue(showTime?.defaultValue)) {
          const [start, end] = showTime.defaultValue;
          value[0].set(getHourMinuteSecond(start));
          value[1].set(getHourMinuteSecond(end));
        }

        const timeStart = getValidTimeValue(value[0], {
          range: getTimeRange(value[0], range),
          ...disabledTime(value, "start"),
          hourStep: showTime?.hourStep || 1,
          minuteStep: showTime?.minuteStep || 1,
          secondStep: showTime?.secondStep || 1,
        });
        const timeEnd = getValidTimeValue(value[1], {
          range: getTimeRange(value[1], range),
          ...disabledTime(value, "end"),
          hourStep: showTime?.hourStep || 1,
          minuteStep: showTime?.minuteStep || 1,
          secondStep: showTime?.secondStep || 1,
        });

        value[0].set(getHourMinuteSecond(timeStart));
        value[1].set(getHourMinuteSecond(timeEnd));
      }

      preValidValueRef.current = value;
      return value;
    }

    /**
     * 获取 ShowTime 参数
     * @param index 左/右面板 - 0/1
     */
    function getShowTime(index: number): showTimeType<Moment> {
      if (typeof showTime === "object") {
        if (Array.isArray(showTime.defaultValue)) {
          return { ...showTime, defaultValue: showTime.defaultValue[index] };
        }
        return showTime as showTimeType<Moment>;
      }
      return !!showTime;
    }

    /**
     * 获取左右面板 range
     */
    function getRange(range): [RangeDateType, RangeDateType] {
      const [rangeMin, rangeMax] = range || [null, null];
      const endPreEnd = moment(curEndView)
        .subtract(1, parentUnit)
        .endOf(parentUnit);
      const startNxtStart = moment(curStartView)
        .add(1, parentUnit)
        .startOf(parentUnit);

      return [
        [rangeMin, isAfter(endPreEnd, rangeMax) ? rangeMax : endPreEnd],
        [
          isBefore(startNxtStart, rangeMin) ? rangeMin : startNxtStart,
          rangeMax,
        ],
      ];
    }

    /**
     * 获取面板默认展示时间
     * @param index 左/右面板 - 0/1
     */
    function getDefaultViewMoment(index: number): Moment {
      const { range } = props;
      const showTime = getShowTime(index);
      const time =
        showTime && typeof showTime === "object"
          ? showTime.defaultValue
          : undefined;

      // 没有初始值
      if (!isValidRangeValue(value)) {
        let m;
        if (!time) {
          m = moment();
        } else {
          m = moment(getHourMinuteSecond(time as Moment));
        }
        // range 判断
        if (Array.isArray(range)) {
          let [start, end] = range;
          if (!moment.isMoment(start)) {
            start = moment(0);
          }
          if (!moment.isMoment(end)) {
            end = moment(2 ** 52);
          }
          if (isBefore(end, m, parentUnit)) {
            return index === 0 ? moment(end).subtract(1, parentUnit) : end;
          }
          if (isAfter(start, m, parentUnit)) {
            return index === 0 ? start : moment(start).add(1, parentUnit);
          }
          if (moment.isMoment(start) && isSame(start, m, parentUnit)) {
            return index === 0 ? m : moment(m).add(1, parentUnit);
          }
        }
        return index === 0 ? moment(m).subtract(1, parentUnit) : m;
      }

      // 初始值在同个月/年
      if (isSame(value[0], value[1], parentUnit)) {
        // 根据 range 判断
        if (Array.isArray(range)) {
          // 当前月前一月不可选时，向后显示一月
          if (
            moment.isMoment(range[0]) &&
            isSame(range[0], value[1], parentUnit)
          ) {
            return index === 0 ? value[1] : moment(value[1]).add(1, parentUnit);
          }
        }
        return index === 0
          ? moment(value[1]).subtract(1, parentUnit)
          : value[1];
      }

      return value[index];
    }

    /**
     * 是否改变当前视图时间
     * 连续两月情况下，点击中间交错日期不改变当前视图时间
     * @param from 当前视图变更事件来源
     * @param nextDate 要变更到的时间
     * @param type 当前视图类型
     */
    function shouldCurViewChange(
      from: string,
      nextDate: Moment,
      type: "start" | "end" = "start"
    ): boolean {
      if (from !== "outside-date") {
        return true;
      }
      const diffMonths = (start, end) => {
        const years = end.year() - start.year();
        return end.month() - start.month() + years * 12;
      };
      if (diffMonths(curStartView, curEndView) === 1) {
        if (type === "start") {
          return diffMonths(curStartView, nextDate) !== 1;
        }
        return diffMonths(nextDate, curEndView) !== 1;
      }
      return true;
    }

    function handleStartInputChange(content: string) {
      setInputValue({
        ...(inputValue as RangeInputValue),
        inputValueStart: content,
      });
      let startValue = moment(content, format, true);
      let endValue = moment(
        (inputValue as RangeInputValue).inputValueEnd,
        format,
        true
      );
      let temp;
      if (!startValue.isValid()) {
        onInputValueChange(content, { valid: false });
        return;
      }

      const isValid = calendarType === "month" ? isValidMonth : isValidDate;
      const valid = isValid(startValue, props, endValue);
      const validTime = showTime
        ? isValidTimeValue(startValue, {
            ...showTime,
            ...props,
            ...props.disabledTime?.([startValue, endValue], "start"),
          })
        : true;
      if (valid && validTime) {
        // 输入的开始日期大于结束日期互换
        if (startValue.isAfter(endValue, "d")) {
          temp = endValue;
          endValue = startValue;
          startValue = temp;
        }
        setCurValue([startValue, endValue]);
      }

      onInputValueChange(content, { valid: false });
    }

    function handleEndInputChange(content: string) {
      setInputValue({
        ...(inputValue as RangeInputValue),
        inputValueEnd: content,
      });
      let startValue = moment(
        (inputValue as RangeInputValue).inputValueStart,
        format,
        true
      );
      let endValue = moment(content, format, true);
      let temp;
      if (!endValue.isValid()) {
        onInputValueChange(content, { valid: false });
        return;
      }

      const isValid = calendarType === "month" ? isValidMonth : isValidDate;
      const valid = isValid(endValue, props, startValue);
      const validTime = showTime
        ? isValidTimeValue(endValue, {
            ...showTime,
            ...props,
            ...props.disabledTime?.([startValue, endValue], "end"),
          })
        : true;
      if (valid && validTime) {
        // 输入的结束日期小于开始日期互换
        if (endValue.isBefore(startValue, "d")) {
          temp = endValue;
          endValue = startValue;
          startValue = temp;
        }
        setCurValue([startValue, endValue]);
      }

      onInputValueChange(content, { valid: false });
    }

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

    return (
      <Popover
        trigger={[
          DatePickerTrigger,
          { onOpen: handleOpen, onClose: handleClose },
        ]}
        visible={active}
        onVisibleChange={setActive}
        placement={placement}
        placementOffset={placementOffset}
        closeOnScroll={closeOnScroll}
        escapeWithReference={escapeWithReference}
        popupContainer={popupContainer}
        overlayClassName={overlayClassName}
        overlayStyle={overlayStyle}
        overlay={
          <RangePickerContext.Provider value={{ range: restProps.range }}>
            <DropdownBox preventMouseDown adaptable>
              <CalendarPart.Panel rangeMode timeMode={type[0] === "time"}>
                {!!header && (
                  <CalendarPart.Header>{header}</CalendarPart.Header>
                )}
                <CalendarWrapper
                  {...restProps}
                  showTime={showTime}
                  value={curValue}
                  onChange={handleChange}
                  onOk={handleOk}
                  type={type}
                  onTypeChange={types =>
                    setType(types as [CalendarTableType, CalendarTableType])
                  }
                >
                  {({ type, onTypeChange, range, ...props }) => {
                    const [startRange, endRange] = getRange(range);
                    return (
                      <>
                        <CalendarTable
                          {...props}
                          monthOnly={calendarType === "month"}
                          rangeType="start"
                          range={startRange}
                          type={type[0]}
                          onTypeChange={t => onTypeChange([t, type[1]])}
                          showTime={getShowTime(0)}
                          curViewMoment={curStartView}
                          onCurViewMomentChange={(date, { from } = {}) => {
                            if (!shouldCurViewChange(from, date)) {
                              return;
                            }
                            if (!isAfter(curEndView, date, parentUnit)) {
                              setEndCurView(moment(date).add(1, parentUnit));
                            }
                            setStartCurView(date);
                          }}
                          dateRangeInRangePicker={range || [null, null]}
                          hovered={hovered}
                          onHoveredChange={setHovered}
                        />
                        <CalendarTable
                          {...props}
                          monthOnly={calendarType === "month"}
                          rangeType="end"
                          range={endRange}
                          type={type[1]}
                          onTypeChange={t => onTypeChange([type[0], t])}
                          showTime={getShowTime(1)}
                          curViewMoment={curEndView}
                          onCurViewMomentChange={(date, { from } = {}) => {
                            if (!shouldCurViewChange(from, date, "end")) {
                              return;
                            }
                            if (!isBefore(curStartView, date, parentUnit)) {
                              setStartCurView(
                                moment(date).subtract(1, parentUnit)
                              );
                            }
                            setEndCurView(date);
                          }}
                          dateRangeInRangePicker={range || [null, null]}
                          hovered={hovered}
                          onHoveredChange={setHovered}
                        />
                      </>
                    );
                  }}
                </CalendarWrapper>
              </CalendarPart.Panel>
            </DropdownBox>
          </RangePickerContext.Provider>
        }
      >
        <div
          ref={ref}
          className={classNames(
            `${classPrefix}-date${showTime ? "time" : ""}picker`,
            className,
            {
              "is-disabled": disabled,
            }
          )}
          style={style}
          {...mergeEventProps(props)}
        >
          <div
            className={classNames(
              `${classPrefix}-date${showTime ? "time" : ""}picker__input`,
              `${classPrefix}-datepicker__input--range`,
              {
                "is-hover": hover && clearable && isValidRangeValue(value),
                [`${classPrefix}-datetimepicker__input--millisecond`]:
                  showTime && showMilliseconds,
              }
            )}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
          >
            <Input
              ref={startInputRef}
              disabled={disabled}
              placeholder={
                Array.isArray(placeholder) ? placeholder[0] : placeholder
              }
              value={
                typeof inputValue === "string"
                  ? inputValue
                  : inputValue.inputValueStart
              }
              onChange={handleStartInputChange}
              onKeyDown={handleKeyDown}
              onFocus={() =>
                calendarType === "quarter" && startInputRef.current.blur()
              }
              onClick={(event: React.MouseEvent) => {
                if (active) {
                  event.stopPropagation();
                }
              }}
            />
            {typeof inputValue !== "string" && (
              <span>{inputValue.separator}</span>
            )}
            <Input
              ref={endInputRef}
              disabled={disabled}
              placeholder={
                Array.isArray(placeholder) ? placeholder[1] : placeholder
              }
              value={
                typeof inputValue === "string"
                  ? inputValue
                  : inputValue.inputValueEnd
              }
              onChange={handleEndInputChange}
              onKeyDown={handleKeyDown}
              onFocus={() =>
                calendarType === "quarter" && endInputRef.current.blur()
              }
              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" }
);

RangePicker.displayName = "RangePicker";
