/**
 * 日历组件
 */
import React from 'react'
import SwipeableViews from 'react-swipeable-views'
import { virtualize } from 'react-swipeable-views-utils'

import SingleMonth from './SingleMonth'
import { CalendarEventList } from './type'
import WeekTitle from './WeekTitle'
import { inMonth, monthDiff, Today } from './utils'

export * from './utils'
export * from './type'
export { SingleMonth }

export interface CalendarProps {
  date?: Date
  onChange?: (date: Date) => void
  onMonthChange?: (date: Date) => void
  // 获取制定月份的事件列表
  onGetEventsForMonth?: (date: Date) => Promise<void>
  // 事件，key为year-month
  events?: { [yearmonth: string]: { [date: string]: any[] | undefined } }
  // 法定节假日
  legalHolidayEvents?: { [yearmonth: string]: CalendarEventList }
  animateHeight?: boolean
  extraData?: any
  weekTitleStyle?: React.CSSProperties
  monthStyle?: React.CSSProperties
}

interface State {
  base: Date
  index: number
  suppressError: boolean
}

const Swipe = virtualize(SwipeableViews)

export default class Calendar extends React.Component<CalendarProps, State> {
  /**
   * 基准日期。日历swiper根据这个基准日期为index，向前向后计算日期
   * 默认为惊天
   */
  public state: State = {
    base: Today,
    index: 0,
    suppressError: false,
  }

  private monthCached: { [index: string]: Date } = {}

  public constructor(props: CalendarProps) {
    super(props)
    const suppressError = window.sessionStorage.getItem('suppressCalendarError')
    if (suppressError) {
      this.setState({
        suppressError: suppressError === 'true',
      })
    }
  }

  public componentDidMount() {
    if (this.props.date) {
      this.rebaseIfNeed(Today, this.props.date)
    }
  }

  public componentDidUpdate(preProps: CalendarProps) {
    if (preProps.date !== this.props.date) {
      this.rebaseIfNeed(preProps.date, this.props.date)
    }
  }

  public render() {
    const { date, weekTitleStyle, extraData, animateHeight } = this.props
    const { base, index, suppressError } = this.state
    return (
      <div className="jm-calendar">
        <WeekTitle style={weekTitleStyle} />
        <Swipe
          // base 变动应该重新渲染日历
          key={base.getTime()}
          className="jm-calendar__swipe"
          slideRenderer={this.renderMonth}
          overscanSlideAfter={4}
          overscanSlideBefore={3}
          index={index}
          onChangeIndex={this.handleIndexChange}
          animateHeight={animateHeight}
          enableMouseEvents
          // 触发下层更新
          data-date={date}
          data-showerror={!suppressError}
          data-extra={extraData}
        />
      </div>
    )
  }

  public navigate2Today = () => {
    const { onChange } = this.props
    if (onChange) {
      onChange(Today)
    }
  }

  /**
   * 如果日期变动超过两个月。即不是正常的滑动行为，这时候应该重设base
   * @param prev
   * @param current
   */
  private rebaseIfNeed(prev: Date = Today, current: Date = Today) {
    const diff = monthDiff(prev, current)

    // 跨度
    if (Math.abs(diff) > 2) {
      // 清空缓存
      this.monthCached = {}
      this.setState({ base: current })
    }

    this.resetIndex(current)
  }

  private resetIndex = (date: Date) => {
    // 检查是不否需要更新index
    const diff = monthDiff(date!, this.state.base)
    if (this.state.index !== diff) {
      this.setState({ index: diff })
      if (this.props.onMonthChange) {
        this.props.onMonthChange(date)
      }
    }
  }

  private renderMonth = (params: { index: number; key: number }) => {
    const {
      date,
      onChange,
      monthStyle,
      onGetEventsForMonth,
      events,
      legalHolidayEvents,
    } = this.props
    const { base, suppressError } = this.state
    // 相对于当前月渲染slide
    const month = this.getMonth(params.index, base)
    const yearMonth = `${month.getFullYear()}-${month.getMonth() + 1}`
    const eventsForMonth = events && events[yearMonth]
    const legalHolidayEventsForMonth =
      legalHolidayEvents && legalHolidayEvents[yearMonth]

    return (
      <SingleMonth
        key={params.key}
        date={month}
        showGetEventError={!suppressError}
        onGetEventsForMonth={onGetEventsForMonth}
        events={eventsForMonth}
        legalHolidayEvents={legalHolidayEventsForMonth}
        onSuppressError={this.handleSuppressError}
        currentSelect={date}
        onSelect={onChange}
        style={monthStyle}
      />
    )
  }

  private handleSuppressError = () => {
    this.setState({ suppressError: true })
    window.sessionStorage.setItem('suppressCalendarError', 'true')
  }

  private handleIndexChange = (index: number) => {
    const { date, onChange } = this.props
    const month = this.getMonth(index, this.state.base)
    let newDate: Date | undefined = date
    if (inMonth(month, Today)) {
      newDate = Today
    } else {
      const startOfMonth = new Date(month)
      startOfMonth.setDate(1)
      newDate = startOfMonth
    }

    // 切换之后，存在一定几率会导致触摸穿透, 即点击到日历，导致日期还停留在以前
    // 所以这里延后触发onChange事件
    this.changeIndex(index, newDate!)
    window.setTimeout(() => {
      if (onChange) {
        onChange(newDate!)
      }
    }, 200)
  }

  private changeIndex(index: number, date: Date) {
    this.setState({ index })
    if (this.props.onMonthChange) {
      this.props.onMonthChange(date)
    }
  }

  private getMonth = (index: number, relative: Date) => {
    if (this.monthCached[index]) {
      return this.monthCached[index]
    }

    const month = new Date(relative)
    month.setDate(1)
    month.setMonth(month.getMonth() + index)
    return (this.monthCached[index] = month)
  }
}
