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

export interface DatePickerProps
  extends CommonDatePickerProps,
    ControlledProps<Moment> {
  /**
   * 是否开启时间选择
   */
  showTime?: showTimeType<Moment>;

  /**
   * 设置不可选日期，返回：
   *
   * - `true` 可选
   * - `false` 不可选
   */
  disabledDate?: (date: Moment) => boolean;

  /**
   * 设置不可选时间
   */
  disabledTime?: (date: Moment) => TimeDisabledProps;
}

const getFormat = showTime => (showTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD");

export const DatePicker = forwardRefWithStatics(
  function DatePicker(props: DatePickerProps, ref: React.Ref<HTMLDivElement>) {
    const t = useTranslation(moment);
    const { classPrefix } = useConfig();
    const {
      header,
      className,
      style,
      showTime,
      value,
      onChange,
      disabled,
      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,
      ...restProps
    } = useDefaultValue(props);

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

    const format = _format || getFormat(showTime);

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

    // 当前面板展示时间
    const [curViewMoment, setCurViewMoment] = useState<Moment>(
      moment.isMoment(value) ? value : getDefaultViewMoment()
    );

    // 当前选中日期
    const [curValue, setCurValue] = useState<Moment>(value);

    // 上次选中日期
    const preValidValueRef = useRef<Moment>(value);

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

    useEffect(() => {
      setCurValue(value);
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [active]);

    // 输入框显示值
    const inputRef = useRef<HTMLInputElement>(null);
    const getInputValue = useCallback(
      value => {
        return moment.isMoment(value)
          ? value.locale(t.locale).format(format)
          : "";
      },
      [t.locale, format]
    );
    const [inputValue, setInputValue] = useState<string>(
      getInputValue(curValue)
    );

    const syncInputValue = useCallback(
      (value: Moment) => {
        const inputValue = getInputValue(value);
        setInputValue(inputValue);
        onInputValueChange(inputValue, { valid: true });
      },
      // eslint-disable-next-line react-hooks/exhaustive-deps
      [getInputValue]
    );

    useEffect(() => {
      setCurValue(value);
      syncInputValue(value);
    }, [format, value, getInputValue, syncInputValue]);

    useEffect(() => {
      setCurViewMoment(
        moment.isMoment(curValue) ? curValue : getDefaultViewMoment()
      );
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [curValue]);

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

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

      setCurValue(value);
      syncInputValue(value);

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

    function handleOk(event): void {
      if (curValue) {
        onChange(curValue, { event });
        handleClose();
      }
    }

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

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

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

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

      preValidValueRef.current = value;
      return value;
    }

    /**
     * 获取面板默认展示时间
     */
    function getDefaultViewMoment(): Moment {
      const { range } = props;
      const time =
        showTime && typeof showTime === "object"
          ? showTime.defaultValue
          : undefined;

      let m = null;
      if (!time) {
        m = moment();
      } else {
        m = moment(getHourMinuteSecond(time));
      }
      // 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, "month")) {
          return end;
        }
        if (isAfter(start, m, "month")) {
          return start;
        }
      }
      return m;
    }

    function handleInputChange(content: string) {
      setInputValue(content);
      const value = moment(content, format, true);
      if (!value.isValid()) {
        onInputValueChange(content, { valid: false });
        return;
      }

      const valid = isValidDate(value, props);
      const validTime = showTime
        ? isValidTimeValue(value, { ...showTime, ...props })
        : true;
      if (valid && validTime) {
        setCurValue(value);
      }

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

    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={
          <DropdownBox preventMouseDown>
            <CalendarPart.Panel timeMode={type === "time"}>
              {!!header && <CalendarPart.Header>{header}</CalendarPart.Header>}
              <TimeSupportWrapper
                {...restProps}
                showTime={showTime}
                value={curValue}
                onChange={handleChange}
                onOk={handleOk}
                type={type}
                onTypeChange={type => setType(type as CalendarTableType)}
              >
                {props => (
                  <CalendarTable
                    {...props}
                    curViewMoment={curViewMoment}
                    onCurViewMomentChange={setCurViewMoment}
                  />
                )}
              </TimeSupportWrapper>
            </CalendarPart.Panel>
          </DropdownBox>
        }
      >
        <div
          {...ref}
          className={classNames(`${classPrefix}-datepicker`, className)}
          style={style}
          {...mergeEventProps(props)}
        >
          <div
            className={classNames(`${classPrefix}-datepicker__input`, {
              "is-hover": hover && value && clearable,
            })}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
          >
            <Input
              ref={inputRef}
              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, { event });
                }}
              />
            )}
          </div>
        </div>
      </Popover>
    );
  },
  {
    RangePicker,
    MonthPicker,
    QuarterPicker,
    defaultLabelAlign: "middle",
  }
);

DatePicker.displayName = "DatePicker";
