// @ts-ignore
import TimeView from 'react-datetime/src/TimeView';
import moment, { Moment } from 'moment';
import React from 'react';
import { LocaleProps, localeable } from '../../locale';
import { Icon } from '../icons';
import { ClassNamesFn } from '../../theme';
import Picker from '../Picker';
import { PickerColumnItem } from '../PickerColumn';
import { getRange, isMobile, isObject } from '../../utils/helper';
import Downshift from 'downshift';

interface State {
  daypart: any;
  counters: Array<string>;
  [propName: string]: any;
}

interface CustomTimeViewProps {
  viewDate: moment.Moment;
  selectedDate: moment.Moment;
  minDate: moment.Moment;
  subtractTime: (
    amount: number,
    type: string,
    toSelected?: moment.Moment
  ) => () => void;
  addTime: (
    amount: number,
    type: string,
    toSelected?: moment.Moment
  ) => () => void;
  showView: (view: string) => () => void;
  timeFormat: string;
  classnames: ClassNamesFn;
  setTime: (type: string, value: any) => void;
  onClose?: () => void;
  onConfirm?: (value: number[], types: string[], isHandleChange?: boolean) => void;
  useMobileUI: boolean;
  showToolbar?: boolean;
  onChange?: (value: any) => void;
  timeConstraints?: any;
  timeRangeHeader?: string;
  isEndDate?: boolean;
  setDateTimeState: (state: any, callback?: () => void) => void;
  requiredConfirm?: boolean;
  type?: string;
};

export type TimeScale = 'hours' | 'minutes' | 'seconds' | 'milliseconds';

export class CustomTimeView extends TimeView {
  props: CustomTimeViewProps & LocaleProps;
  onStartClicking: any;
  disableContextMenu: any;
  updateMilli: any;
  renderHeader: any;
  pad: any;
  state: State;
  timeConstraints = {
    hours: {
      min: 0,
      max: 23,
      step: 1
    },
    minutes: {
      min: 0,
      max: 59,
      step: 1
    },
    seconds: {
      min: 0,
      max: 59,
      step: 1
    },
    milliseconds: {
      min: 0,
      max: 999,
      step: 1
    }
  };
  /** 基于 timeConstraints 计算出的时间列表 */
  timeList: string[] = [];
  padValues = {
    hours: 2,
    minutes: 2,
    seconds: 2,
    milliseconds: 3
  };
  setState: (arg0: any) => () => any;
  calculateState: (props: CustomTimeViewProps) => () => any;

  static defaultProps = {
    showToolbar: true
  };


  componentWillReceiveProps(nextProps: CustomTimeViewProps) {
    if (nextProps.viewDate !== this.props.viewDate
      || nextProps.selectedDate !== this.props.selectedDate
      || nextProps.timeFormat !== this.props.timeFormat) {
      this.setState(this.calculateState(nextProps));
    }
  }

  shoudExtractTimeConstraintsList = (): boolean => {
    const timeConstraints = this.timeConstraints;
    let result = false;

    if (!timeConstraints || !isObject(timeConstraints)) {
      return result;
    }

    ['hours', 'minutes', 'seconds'].forEach(
      (timeScale: 'hours' | 'minutes' | 'seconds') => {
        const { min, max, step } = timeConstraints[timeScale];

        if (result !== false) {
          return;
        }

        if (timeScale === 'hours') {
          result = min > 0 || max < 23 || step > 1;
        } else if (timeScale === 'minutes') {
          result = min > 0 || max < 59 || step > 1;
        } else if (timeScale === 'seconds') {
          result = min > 0 || max < 59 || step > 1;
        }
      }
    );

    return result;
  };

  renderDayPart = () => {
    const { translate: __, classnames: cx } = this.props;
    return (
      <div
        key="dayPart"
        className={cx('CalendarCounter CalendarCounter--daypart')}
      >
        <span
          key="up"
          className={cx('CalendarCounter-btn CalendarCounter-btn--up')}
          onClick={this.onStartClicking('toggleDayPart', 'hours')}
          onContextMenu={this.disableContextMenu}
        >
          <Icon icon="right-arrow-bold" />
        </span>
        <div className={cx('CalendarCounter-value')} key={this.state.daypart}>
          {__(this.state.daypart)}
        </div>
        <span
          key="down"
          className={cx('CalendarCounter-btn CalendarCounter-btn--down')}
          onClick={this.onStartClicking('toggleDayPart', 'hours')}
          onContextMenu={this.disableContextMenu}
        >
          <Icon icon="right-arrow-bold" />
        </span>
      </div>
    );
  };

  getCounterValue = (type: string) => {
    if (type !== 'daypart') {
      let value = this.state[type];
      if (
        type === 'hours' &&
        this.props.timeFormat.toLowerCase().indexOf(' a') !== -1
      ) {
        value = ((value - 1) % 12) + 1;

        if (value === 0) {
          value = 12;
        }
      }
      return parseInt(value);
    }
    return 0;
  };

  renderCounter = (type: string) => {
    const cx = this.props.classnames;
    if (type !== 'daypart') {
      const value = this.getCounterValue(type);

      const { min, max, step } = this.timeConstraints[type];

      return (
        <div key={type} className={cx('CalendarCounter')}>
          <span
            key="up"
            className={cx('CalendarCounter-btn CalendarCounter-btn--up')}
            onMouseDown={this.onStartClicking('increase', type)}
            onContextMenu={this.disableContextMenu}
          >
            <Icon icon="right-arrow-bold" />
          </span>

          <div key="c" className={cx('CalendarCounter-value')}>
            <input
              type="text"
              value={this.pad(type, value)}
              className={cx('CalendarInput')}
              min={min}
              max={max}
              step={step}
              onChange={e =>
                this.props.setTime(
                  type,
                  Math.max(
                    min,
                    Math.min(
                      parseInt(e.currentTarget.value.replace(/\D/g, ''), 10) ||
                      0,
                      max
                    )
                  )
                )
              }
            />
          </div>

          <span
            key="do"
            className={cx('CalendarCounter-btn CalendarCounter-btn--down')}
            onMouseDown={this.onStartClicking('decrease', type)}
            onContextMenu={this.disableContextMenu}
          >
            <Icon icon="right-arrow-bold" />
          </span>
        </div>
      );
    }
    return null;
  };

  onConfirm = (value: (number | string)[], isHandleChange?: boolean) => {
    // 修正am、pm
    const hourIndex = this.state.counters.indexOf('hours');
    if (
      hourIndex !== -1 &&
      this.state.daypart !== false &&
      this.props.timeFormat.toLowerCase().indexOf(' a') !== -1
    ) {
      const amMode: string = value.splice(-1, 1)[0] as string;
      let hour = (value[hourIndex] as number) % 12;
      // 修正pm
      amMode.toLowerCase().indexOf('p') !== -1 && (hour = hour + 12);
      value[hourIndex] = hour;
    }

    this.props.onConfirm && this.props.onConfirm(value as number[], this.state.counters, isHandleChange);
  };

  getDayPartOptions = () => {
    const { translate: __ } = this.props;
    let options = ['am', 'pm'];
    if (this.props.timeFormat.indexOf(' A') !== -1) {
      options = ['AM', 'PM'];
    }

    return options.map(daypart => ({
      text: __(daypart),
      value: daypart
    }));
  };

  onPickerChange = (value: (number | string)[], index: number) => {
    const time: { [prop: string]: any } = {};
    this.state.counters.forEach((type, i) => time[type] = value[i]);
    if (this.state.daypart !== false && index > this.state.counters.length - 1) {
      time.daypart = value[value.length - 1];
    }
    this.setState((prevState: State) => {
      return { ...prevState, ...time }
    });
    // this.props.onChange && this.props.onChange(value);
  }

  renderTimeViewPicker = () => {
    const { translate: __, type } = this.props;
    const title = __('Date.titleTime');
    const columns: PickerColumnItem[] = [];
    const values = [];
    this.state.counters.forEach(type => {
      if (type !== 'daypart') {
        let { min, max, step } = this.timeConstraints[type];
        // 修正am pm时hours可选最大值
        if (
          type === 'hours' &&
          this.state.daypart !== false &&
          this.props.timeFormat.toLowerCase().indexOf(' a') !== -1
        ) {
          max = max > 12 ? 12 : max;
        }
        columns.push({
          options: getRange(min, max, step).map(item => {
            return {
              text: this.pad(type, item),
              value: item
            }
          }),
          type: __('Data.' + type)
        });
        values.push(parseInt(this.state[type], 10));
      }
    });
    if (this.state.daypart !== false) {
      columns.push({
        options: this.getDayPartOptions()
      });
      values.push(this.state.daypart);
    }

    return (
      <Picker
        translate={this.props.translate}
        locale={this.props.locale}
        title={title}
        columns={columns}
        value={values}
        onConfirm={this.onConfirm}
        onClose={this.props.onClose}
        showToolbar={this.props.showToolbar}
        onChange={this.onPickerChange}
        footer
        type={type}
      />
    );
  };

  componentDidMount() {
    const { timeFormat, selectedDate, viewDate, isEndDate } = this.props;
    const date = selectedDate || (isEndDate ? viewDate.endOf('day') : viewDate);
    this.setupTime(date, timeFormat, 'init');
  }

  setupTime = (date: Moment, timeFormat: string, mode?: 'init') => {
    const formatMap = {
      hours: 'HH',
      minutes: 'mm',
      seconds: 'ss'
    };
    timeFormat.split(':').forEach((format, i) => {
      const type = /h/i.test(format)
        ? 'hours'
        : /m/.test(format)
          ? 'minutes'
          : /s/.test(format)
            ? 'seconds'
            : '';
      if (type) {
        this.scrollToTop(
          type,
          parseInt(date.format(formatMap[type]), 10),
          i,
          mode
        );
      }
    });
  }

  setTime = (type: TimeScale, value: number) => {
    const date = (this.props.selectedDate || this.props.viewDate).clone();
    date[type](value);

    this.props.setDateTimeState({
      viewDate: date.clone(),
      selectedDate: date.clone()
    });
    if (!this.props.requiredConfirm) {
      this.props.onChange?.(date);
    }
  };

  scrollToTop = (type: TimeScale, value: number, i: number, label?: string) => {
    const elf: any = document.getElementById(
      `${this.state.uniqueTag}-${i}-input`
    );
    const { min, step } = this.timeConstraints[type];
    const offset = (value - min) / step;
    const height = 28; /** 单个选项的高度 */

    elf?.parentNode?.scrollTo?.({
      top: offset * height,
      behavior: label === 'init' ? 'auto' : 'smooth'
    });
  };

  /**
   * 选择当前时间，如果设置了timeConstraints，则选择最接近的时间
   */
  selectNowTime = () => {
    const { setDateTimeState, timeFormat } = this.props;
    const useClosetDate = this.shoudExtractTimeConstraintsList();

    if (useClosetDate) {
      const timeList = this.timeList;
      const now: Moment = moment().clone();
      let closetDate: Moment = now.clone();
      let minDiff = Infinity;

      /** 遍历时间列表，找出最接近此刻的时间 */
      timeList.forEach((item, index) => {
        const date = moment(item, timeFormat);
        const diff = Math.abs(now.diff(date));

        if (diff < minDiff) {
          minDiff = diff;
          closetDate = date;
        }
      });

      setDateTimeState({ viewDate: closetDate, selectedDate: closetDate }, () =>
        this.confirm()
      );
    } else {
      setDateTimeState(
        { viewDate: moment().clone(), selectedDate: moment().clone() },
        () => this.confirm()
      );
    }
  };

  confirm = () => {
    let date = (this.props.selectedDate || this.props.viewDate).clone();

    // 如果 minDate 是可用的，且比当前日期晚，则用 minDate
    if (this.props.minDate?.isValid() && this.props.minDate?.isAfter(date)) {
      date = this.props.minDate.clone();
    }

    this.props.setDateTimeState({
      selectedDate: date
    });
    this.props.onChange?.(date);
    this.props.onClose && this.props.onClose();
  };

  cancel = () => {
    this.props.onClose && this.props.onClose();
  };

  computedTimeOptions = (timeScale: TimeScale) => {
    const { min, max, step } = this.timeConstraints?.[timeScale];

    return Array.from({ length: max - min + 1 }, (item, index) => {
      const value = (index + min)
        .toString()
        .padStart(timeScale !== 'milliseconds' ? 2 : 3, '0');

      return index % step === 0 ? { label: value, value } : undefined;
    }).filter((item): item is { label: string; value: string } => !!item);
  }

  render() {
    const { classnames: cx, timeFormat, selectedDate, viewDate, timeRangeHeader, translate: __,
      isEndDate, requiredConfirm } = this.props;
    const date = selectedDate || (isEndDate ? viewDate.endOf('day') : viewDate);
    const inputs: Array<React.ReactNode> = [];
    if (isMobile()) {
      return (
        <div className={cx('CalendarTime')}>{this.renderTimeViewPicker()}</div>
      );
    }
    timeFormat.split(':').forEach((format, i) => {
      const type = /h/i.test(format)
        ? 'hours'
        : /m/.test(format)
          ? 'minutes'
          : /s/.test(format)
            ? 'seconds'
            : '';
      if (type) {
        const options = this.computedTimeOptions(type);
        const formatMap = {
          hours: 'HH',
          minutes: 'mm',
          seconds: 'ss'
        };

        inputs.push(
          <Downshift
            key={i + 'input'}
            inputValue={date.format(formatMap[type])}
          >
            {({ closeMenu }) => {
              return (
                <div className={cx('CalendarInputWrapper')}>
                  <div
                    className={cx(
                      'CalendarInput-sugs',
                      type === 'hours'
                        ? 'CalendarInput-sugsHours'
                        : 'CalendarInput-sugsTimes'
                    )}
                    id={`${this.state.uniqueTag}-${i}-input`}
                  >
                    {options.map(option => {
                      return (
                        <div
                          key={option.value}
                          className={cx('CalendarInput-sugsItem', {
                            'is-mobile': isMobile(),
                            'is-highlight': option.value === date.format(formatMap[type])
                          })}
                          onClick={() => {
                            this.setTime(type, parseInt(option.value, 10));
                            this.scrollToTop(
                              type,
                              parseInt(option.value, 10),
                              i
                            );
                            closeMenu();
                          }}
                        >
                          {option.value}
                        </div>
                      );
                    })}
                  </div>
                </div>
              );
            }}
          </Downshift>
        );
        inputs.push(<span key={i + 'divider'}></span>);
      }
    });
    inputs.length && inputs.pop();

    const quickLists = [
      <a key="select-now" onClick={this.selectNowTime}>
        {__('TimeNow')}
      </a>
    ];

    return (<>
      <div className={cx(timeRangeHeader ? 'TimeRangeHeaderWrapper' : null)}>
        {timeRangeHeader}
      </div>
      <div className={cx('TimeContentWrapper')}>{inputs}</div>
      {requiredConfirm && <div className={cx('TimeFooterWrapper')}>
        <div className={cx('QuickWrapper')}>{quickLists}</div>
        <a
          className={cx('Button', 'Button--primary', 'Button--size-sm')}
          onClick={this.confirm}
        >
          {__('confirm')}
        </a>
      </div>}
    </>)
  }
}

export default localeable(CustomTimeView as any);
