/**
 * 时间选择器
 */
import React from 'react'
import MultiPicker from '@gdjiami/rmc-picker/es/MultiPicker'
import Picker from '@gdjiami/rmc-picker/es/Picker'
import Modal from 'antd-mobile/es/modal'
import { isSameDay, Today, isSameHour } from '../calendar'
import { format, formatTime } from '../date-picker/utils'

export interface DateTimePickerProps {
  value?: Date
  title?: string
  onChange?: (value: Date) => void
  // 设定可选择的区间
  disabledBefore?: Date
  disabledAfter?: Date
  children: (value: Date, onClick: () => void) => React.ReactNode
}

type NormalizeValue = [string, number, number]
type DateList = {
  before?: Date
  after?: Date
  list: Array<{ label: string; value: string }>
}
type LabelList = Array<{ label: string; value: number }>
type HourList = {
  before: LabelList
  after: LabelList
  normal: LabelList
  inSameComponent: boolean
}
type MinuteList = HourList

interface State {
  base: Date
  lastValue?: Date
  value: NormalizeValue
  visible?: boolean
  dateList: DateList
  hourList: HourList
  minuteList: MinuteList
}

const defaultDisabledBefore = new Date(0)
const defaultDisabledAfter = new Date(2100, 1, 1)

export default class DateTimePicker extends React.Component<
  DateTimePickerProps
> {
  public state: State = {
    dateList: genDateList(
      this.props.value || Today,
      undefined,
      this.props.disabledBefore,
      this.props.disabledAfter,
    ),
    hourList: genHourList(this.props.disabledBefore, this.props.disabledAfter),
    minuteList: genMinList(this.props.disabledBefore, this.props.disabledAfter),
    visible: false,
    base: this.props.value || Today,
    value: normalizeValue(this.props.value || Today),
  }

  public static defaultProps = {
    title: '',
  }

  public static getDerivedStateFromProps(
    nextProps: DateTimePickerProps,
    prevState: State,
  ) {
    const res: Partial<State> = {}
    if (
      nextProps.value &&
      ((nextProps.value !== prevState.base &&
        !isSameDay(prevState.base, nextProps.value)) ||
        nextProps.disabledBefore !== prevState.dateList.before ||
        nextProps.disabledAfter !== prevState.dateList.after)
    ) {
      res.dateList = genDateList(
        nextProps.value,
        undefined,
        nextProps.disabledBefore,
        nextProps.disabledAfter,
      )
    }

    if (
      nextProps.disabledBefore !== prevState.dateList.before ||
      nextProps.disabledAfter !== prevState.dateList.after
    ) {
      res.hourList = genHourList(
        nextProps.disabledBefore,
        nextProps.disabledAfter,
      )
      res.minuteList = genMinList(
        nextProps.disabledBefore,
        nextProps.disabledAfter,
      )
    }

    if (nextProps.value && nextProps.value !== prevState.base) {
      res.value = normalizeValue(nextProps.value)
      res.base = nextProps.value
    }

    return res
  }

  public render() {
    const { children, title } = this.props
    const { dateList, visible, value, base } = this.state
    return (
      <>
        {children(base, this.handleShow)}
        <Modal
          transparent
          maskClosable={false}
          visible={!!visible}
          className="jm-date-picker"
          wrapClassName="jm-date-picker__wrapper"
        >
          <div className="jm-date-picker__header-bar">
            <div className="jm-date-picker__action" onClick={this.handleCancel}>
              取消
            </div>
            <div className="jm-date-picker__title">{title}</div>
            <div className="jm-date-picker__action" onClick={this.handleOk}>
              确定
            </div>
          </div>
          <header className="jm-date-picker__header">
            <div className="jm-date-picker__date-display">
              {format(base!, false, true)}
            </div>
            <div className="jm-date-picker__time-display">
              {formatTime(base!)}
            </div>
          </header>
          <MultiPicker
            selectedValue={value}
            onValueChange={this.handleSelectChange}
            className="jm-date-picker__body"
          >
            <Picker className="jm-date-picker__date">
              {dateList.list.map(i => (
                <Picker.Item value={i.value} key={i.label}>
                  {i.label}
                </Picker.Item>
              ))}
            </Picker>
            <Picker>{this.renderHour()}</Picker>
            <Picker>{this.renderMin()}</Picker>
          </MultiPicker>
        </Modal>
      </>
    )
  }

  private renderHour() {
    const { disabledAfter, disabledBefore } = this.props
    const { hourList, base } = this.state
    const list =
      disabledBefore && isSameDay(disabledBefore, base)
        ? hourList.before
        : disabledAfter && isSameDay(disabledAfter, base)
        ? hourList.after
        : hourList.normal

    return list.map(i => (
      <Picker.Item value={i.value} key={i.label}>
        {i.label}
      </Picker.Item>
    ))
  }

  private renderMin() {
    const { disabledAfter, disabledBefore } = this.props
    const { minuteList, base } = this.state
    const list =
      disabledBefore && isSameHour(disabledBefore, base)
        ? minuteList.before
        : disabledAfter && isSameHour(disabledAfter, base)
        ? minuteList.after
        : minuteList.normal

    return list.map(i => (
      <Picker.Item value={i.value} key={i.label}>
        {i.label}
      </Picker.Item>
    ))
  }

  private handleOk = () => {
    this.setState({ visible: false })
  }

  private handleSelectChange = ([date, hour, min]: [
    string,
    number,
    number
  ]) => {
    const value = parseValue(date)
    value.setHours(hour)
    value.setMinutes(min)

    if (this.props.onChange) {
      this.props.onChange(value)
    }
  }

  private handleShow = () => {
    this.setState({ visible: true, lastValue: this.props.value })
  }

  private handleCancel = () => {
    this.setState({
      visible: false,
    })
    if (this.props.onChange) {
      this.props.onChange(this.state.lastValue!)
    }
  }
}

/**
 * 将时间转换为picker可以接受的形式
 */
function normalizeValue(date: Date): NormalizeValue {
  return [genValue(date), date.getHours(), date.getMinutes()]
}

function genValue(date: Date) {
  return `${date.getFullYear()}/${date.getMonth()}/${date.getDate()}`
}

function parseValue(str: string) {
  const [year, month, date] = str.split('/').map(i => parseInt(i))
  return new Date(year, month, date)
}

/**
 * 生成日期列表
 */
function genDateList(
  date: Date,
  length: number = 100,
  disabledBefore?: Date,
  disabledAfter?: Date,
): DateList {
  const before = disabledBefore || defaultDisabledBefore
  const after = disabledAfter || defaultDisabledAfter
  const list = [{ label: format(date), value: genValue(date) }]
  let prev = new Date(date)
  let next = new Date(date)
  let prevFinished: boolean = false
  let nextFinished: boolean = false

  for (let i = 1; i <= length; i++) {
    if (!prevFinished) {
      prev = new Date(prev)
      prev.setDate(prev.getDate() - 1)
      if (prev < before && !isSameDay(prev, before)) {
        prevFinished = true
      } else {
        list.unshift({ label: format(prev), value: genValue(prev) })
      }
    }

    if (!nextFinished) {
      next = new Date(next)
      next.setDate(next.getDate() + 1)
      if (next > after && !isSameDay(next, after)) {
        nextFinished = true
      } else {
        list.push({ label: format(next), value: genValue(next) })
      }
    }

    if (prevFinished && nextFinished) {
      break
    }
  }

  return { list, before: disabledBefore, after: disabledAfter }
}

function genHourList(
  disabledBefore: Date = defaultDisabledBefore,
  disabledAfter: Date = defaultDisabledAfter,
): HourList {
  let before: LabelList = []
  let after: LabelList = []
  const normal = []
  const sameDay = isSameDay(disabledBefore, disabledAfter)

  for (
    let i = disabledBefore.getHours(),
      l = sameDay ? disabledAfter.getHours() + 1 : 24;
    i < l;
    i++
  ) {
    before.push({
      label: ('0' + i).slice(-2),
      value: i,
    })
  }

  if (sameDay) {
    after = before
  } else {
    for (let i = 0; i < disabledAfter.getHours() + 1; i++) {
      after.push({
        label: ('0' + i).slice(-2),
        value: i,
      })
    }
  }

  for (let i = 0; i < 24; i++) {
    normal.push({
      label: ('0' + i).slice(-2),
      value: i,
    })
  }

  return { before, after, normal, inSameComponent: sameDay }
}

function genMinList(
  disabledBefore: Date = defaultDisabledBefore,
  disabledAfter: Date = defaultDisabledAfter,
) {
  let before: LabelList = []
  let after: LabelList = []
  let normal: LabelList = []
  const sameHour =
    isSameDay(disabledBefore, disabledAfter) &&
    disabledBefore.getHours() === disabledAfter.getHours()

  for (
    let i = disabledBefore.getMinutes() + 1,
      l = sameHour ? disabledAfter.getMinutes() : 60;
    i < l;
    i++
  ) {
    before.push({
      label: ('0' + i).slice(-2),
      value: i,
    })
  }

  if (sameHour) {
    after = before
  } else {
    for (let i = 0; i < disabledAfter.getMinutes(); i++) {
      after.push({
        label: ('0' + i).slice(-2),
        value: i,
      })
    }
  }

  for (let i = 0; i < 60; i++) {
    normal.push({
      label: ('0' + i).slice(-2),
      value: i,
    })
  }

  return { before, after, normal, inSameComponent: sameHour }
}
