<template>
  <view class="uni-datetime-picker">
    <view @click="initTimePicker">
      <slot>
        <view
          class="uni-datetime-picker-timebox-pointer"
          :class="{ 'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border }"
        >
          <text class="uni-datetime-picker-text">{{ time }}</text>
          <view v-if="!time" class="uni-datetime-picker-time">
            <text class="uni-datetime-picker-text">{{ selectTimeText }}</text>
          </view>
        </view>
      </slot>
    </view>
    <view v-if="visible" id="mask" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view>
    <view
      v-if="visible"
      class="uni-datetime-picker-popup"
      :class="[dateShow && timeShow ? '' : 'fix-nvue-height']"
      :style="fixNvueBug"
    >
      <view class="uni-title">
        <text class="uni-datetime-picker-text">{{ selectTimeText }}</text>
      </view>
      <view v-if="dateShow" class="uni-datetime-picker__container-box">
        <picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd" @change="bindDateChange">
          <picker-view-column>
            <view class="uni-datetime-picker-item" v-for="(item, index) in years" :key="index">
              <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
            </view>
          </picker-view-column>
          <picker-view-column>
            <view class="uni-datetime-picker-item" v-for="(item, index) in months" :key="index">
              <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
            </view>
          </picker-view-column>
          <picker-view-column>
            <view class="uni-datetime-picker-item" v-for="(item, index) in days" :key="index">
              <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
            </view>
          </picker-view-column>
        </picker-view>
        <!-- 兼容 nvue 不支持伪类 -->
        <text class="uni-datetime-picker-sign sign-left">-</text>
        <text class="uni-datetime-picker-sign sign-right">-</text>
      </view>
      <view v-if="timeShow" class="uni-datetime-picker__container-box">
        <picker-view
          class="uni-datetime-picker-view"
          :class="[hideSecond ? 'time-hide-second' : '']"
          :indicator-style="indicatorStyle"
          :value="hms"
          @change="bindTimeChange"
        >
          <picker-view-column>
            <view class="uni-datetime-picker-item" v-for="(item, index) in hours" :key="index">
              <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
            </view>
          </picker-view-column>
          <picker-view-column>
            <view class="uni-datetime-picker-item" v-for="(item, index) in minutes" :key="index">
              <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
            </view>
          </picker-view-column>
          <picker-view-column v-if="!hideSecond">
            <view class="uni-datetime-picker-item" v-for="(item, index) in seconds" :key="index">
              <text class="uni-datetime-picker-item">{{ lessThanTen(item) }}</text>
            </view>
          </picker-view-column>
        </picker-view>
        <!-- 兼容 nvue 不支持伪类 -->
        <text class="uni-datetime-picker-sign" :class="[hideSecond ? 'sign-center' : 'sign-left']">:</text>
        <text v-if="!hideSecond" class="uni-datetime-picker-sign sign-right">:</text>
      </view>
      <view class="uni-datetime-picker-btn">
        <view @click="clearTime">
          <text class="uni-datetime-picker-btn-text">{{ clearText }}</text>
        </view>
        <view class="uni-datetime-picker-btn-group">
          <view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">
            <text class="uni-datetime-picker-btn-text">{{ cancelText }}</text>
          </view>
          <view @click="setTime">
            <text class="uni-datetime-picker-btn-text">{{ okText }}</text>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import { initVueI18n } from '@dcloudio/uni-i18n';
import i18nMessages from './i18n/index.js';
const { t } = initVueI18n(i18nMessages);
import { fixIosDateFormat } from './util';

/**
 * DatetimePicker 时间选择器
 * @description 可以同时选择日期和时间的选择器
 * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
 * @property {String} type = [datetime | date | time] 显示模式
 * @property {Boolean} multiple = [true|false] 是否多选
 * @property {String|Number} value 默认值
 * @property {String|Number} start 起始日期或时间
 * @property {String|Number} end 起始日期或时间
 * @property {String} return-type = [timestamp | string]
 * @event {Function} change  选中发生变化触发
 */

export default {
  name: 'UniDatetimePicker',
  data() {
    return {
      indicatorStyle: `height: 50px;`,
      visible: false,
      fixNvueBug: {},
      dateShow: true,
      timeShow: true,
      title: '日期和时间',
      // 输入框当前时间
      time: '',
      // 当前的年月日时分秒
      year: 1920,
      month: 0,
      day: 0,
      hour: 0,
      minute: 0,
      second: 0,
      // 起始时间
      startYear: 1920,
      startMonth: 1,
      startDay: 1,
      startHour: 0,
      startMinute: 0,
      startSecond: 0,
      // 结束时间
      endYear: 2120,
      endMonth: 12,
      endDay: 31,
      endHour: 23,
      endMinute: 59,
      endSecond: 59
    };
  },
  options: {
    // #ifdef MP-TOUTIAO
    virtualHost: false,
    // #endif
    // #ifndef MP-TOUTIAO
    virtualHost: true
    // #endif
  },
  props: {
    type: {
      type: String,
      default: 'datetime'
    },
    value: {
      type: [String, Number],
      default: ''
    },
    modelValue: {
      type: [String, Number],
      default: ''
    },
    start: {
      type: [Number, String],
      default: ''
    },
    end: {
      type: [Number, String],
      default: ''
    },
    returnType: {
      type: String,
      default: 'string'
    },
    disabled: {
      type: [Boolean, String],
      default: false
    },
    border: {
      type: [Boolean, String],
      default: true
    },
    hideSecond: {
      type: [Boolean, String],
      default: false
    }
  },
  watch: {
    // #ifndef VUE3
    value: {
      handler(newVal) {
        if (newVal) {
          this.parseValue(fixIosDateFormat(newVal));
          this.initTime(false);
        } else {
          this.time = '';
          this.parseValue(Date.now());
        }
      },
      immediate: true
    },
    // #endif
    // #ifdef VUE3
    modelValue: {
      handler(newVal) {
        if (newVal) {
          this.parseValue(fixIosDateFormat(newVal));
          this.initTime(false);
        } else {
          this.time = '';
          this.parseValue(Date.now());
        }
      },
      immediate: true
    },
    // #endif
    type: {
      handler(newValue) {
        if (newValue === 'date') {
          this.dateShow = true;
          this.timeShow = false;
          this.title = '日期';
        } else if (newValue === 'time') {
          this.dateShow = false;
          this.timeShow = true;
          this.title = '时间';
        } else {
          this.dateShow = true;
          this.timeShow = true;
          this.title = '日期和时间';
        }
      },
      immediate: true
    },
    start: {
      handler(newVal) {
        this.parseDatetimeRange(fixIosDateFormat(newVal), 'start');
      },
      immediate: true
    },
    end: {
      handler(newVal) {
        this.parseDatetimeRange(fixIosDateFormat(newVal), 'end');
      },
      immediate: true
    },

    // 月、日、时、分、秒可选范围变化后，检查当前值是否在范围内，不在则当前值重置为可选范围第一项
    months(newVal) {
      this.checkValue('month', this.month, newVal);
    },
    days(newVal) {
      this.checkValue('day', this.day, newVal);
    },
    hours(newVal) {
      this.checkValue('hour', this.hour, newVal);
    },
    minutes(newVal) {
      this.checkValue('minute', this.minute, newVal);
    },
    seconds(newVal) {
      this.checkValue('second', this.second, newVal);
    }
  },
  computed: {
    // 当前年、月、日、时、分、秒选择范围
    years() {
      return this.getCurrentRange('year');
    },

    months() {
      return this.getCurrentRange('month');
    },

    days() {
      return this.getCurrentRange('day');
    },

    hours() {
      return this.getCurrentRange('hour');
    },

    minutes() {
      return this.getCurrentRange('minute');
    },

    seconds() {
      return this.getCurrentRange('second');
    },

    // picker 当前值数组
    ymd() {
      return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay];
    },
    hms() {
      return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond];
    },

    // 当前 date 是 start
    currentDateIsStart() {
      return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay;
    },

    // 当前 date 是 end
    currentDateIsEnd() {
      return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay;
    },

    // 当前年、月、日、时、分、秒的最小值和最大值
    minYear() {
      return this.startYear;
    },
    maxYear() {
      return this.endYear;
    },
    minMonth() {
      if (this.year === this.startYear) {
        return this.startMonth;
      } else {
        return 1;
      }
    },
    maxMonth() {
      if (this.year === this.endYear) {
        return this.endMonth;
      } else {
        return 12;
      }
    },
    minDay() {
      if (this.year === this.startYear && this.month === this.startMonth) {
        return this.startDay;
      } else {
        return 1;
      }
    },
    maxDay() {
      if (this.year === this.endYear && this.month === this.endMonth) {
        return this.endDay;
      } else {
        return this.daysInMonth(this.year, this.month);
      }
    },
    minHour() {
      if (this.type === 'datetime') {
        if (this.currentDateIsStart) {
          return this.startHour;
        } else {
          return 0;
        }
      }
      if (this.type === 'time') {
        return this.startHour;
      }
    },
    maxHour() {
      if (this.type === 'datetime') {
        if (this.currentDateIsEnd) {
          return this.endHour;
        } else {
          return 23;
        }
      }
      if (this.type === 'time') {
        return this.endHour;
      }
    },
    minMinute() {
      if (this.type === 'datetime') {
        if (this.currentDateIsStart && this.hour === this.startHour) {
          return this.startMinute;
        } else {
          return 0;
        }
      }
      if (this.type === 'time') {
        if (this.hour === this.startHour) {
          return this.startMinute;
        } else {
          return 0;
        }
      }
    },
    maxMinute() {
      if (this.type === 'datetime') {
        if (this.currentDateIsEnd && this.hour === this.endHour) {
          return this.endMinute;
        } else {
          return 59;
        }
      }
      if (this.type === 'time') {
        if (this.hour === this.endHour) {
          return this.endMinute;
        } else {
          return 59;
        }
      }
    },
    minSecond() {
      if (this.type === 'datetime') {
        if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) {
          return this.startSecond;
        } else {
          return 0;
        }
      }
      if (this.type === 'time') {
        if (this.hour === this.startHour && this.minute === this.startMinute) {
          return this.startSecond;
        } else {
          return 0;
        }
      }
    },
    maxSecond() {
      if (this.type === 'datetime') {
        if (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) {
          return this.endSecond;
        } else {
          return 59;
        }
      }
      if (this.type === 'time') {
        if (this.hour === this.endHour && this.minute === this.endMinute) {
          return this.endSecond;
        } else {
          return 59;
        }
      }
    },

    /**
     * for i18n
     */
    selectTimeText() {
      return t('uni-datetime-picker.selectTime');
    },
    okText() {
      return t('uni-datetime-picker.ok');
    },
    clearText() {
      return t('uni-datetime-picker.clear');
    },
    cancelText() {
      return t('uni-datetime-picker.cancel');
    }
  },

  mounted() {
    // #ifdef APP-NVUE
    const res = uni.getSystemInfoSync();
    this.fixNvueBug = {
      top: res.windowHeight / 2,
      left: res.windowWidth / 2
    };
    // #endif
  },

  methods: {
    /**
     * @param {Object} item
     * 小于 10 在前面加个 0
     */

    lessThanTen(item) {
      return item < 10 ? '0' + item : item;
    },

    /**
     * 解析时分秒字符串，例如：00:00:00
     * @param {String} timeString
     */
    parseTimeType(timeString) {
      if (timeString) {
        let timeArr = timeString.split(':');
        this.hour = Number(timeArr[0]);
        this.minute = Number(timeArr[1]);
        this.second = Number(timeArr[2]);
      }
    },

    /**
     * 解析选择器初始值，类型可以是字符串、时间戳，例如：2000-10-02、'08:30:00'、 1610695109000
     * @param {String | Number} datetime
     */
    initPickerValue(datetime) {
      let defaultValue = null;
      if (datetime) {
        defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end);
      } else {
        defaultValue = Date.now();
        defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end);
      }
      this.parseValue(defaultValue);
    },

    /**
     * 初始值规则：
     * - 用户设置初始值 value
     * 	- 设置了起始时间 start、终止时间 end，并 start < value < end，初始值为 value， 否则初始值为 start
     * 	- 只设置了起始时间 start，并 start < value，初始值为 value，否则初始值为 start
     * 	- 只设置了终止时间 end，并 value < end，初始值为 value，否则初始值为 end
     * 	- 无起始终止时间，则初始值为 value
     * - 无初始值 value，则初始值为当前本地时间 Date.now()
     * @param {Object} value
     * @param {Object} dateBase
     */
    compareValueWithStartAndEnd(value, start, end) {
      let winner = null;
      value = this.superTimeStamp(value);
      start = this.superTimeStamp(start);
      end = this.superTimeStamp(end);

      if (start && end) {
        if (value < start) {
          winner = new Date(start);
        } else if (value > end) {
          winner = new Date(end);
        } else {
          winner = new Date(value);
        }
      } else if (start && !end) {
        winner = start <= value ? new Date(value) : new Date(start);
      } else if (!start && end) {
        winner = value <= end ? new Date(value) : new Date(end);
      } else {
        winner = new Date(value);
      }

      return winner;
    },

    /**
     * 转换为可比较的时间戳，接受日期、时分秒、时间戳
     * @param {Object} value
     */
    superTimeStamp(value) {
      let dateBase = '';
      if (this.type === 'time' && value && typeof value === 'string') {
        const now = new Date();
        const year = now.getFullYear();
        const month = now.getMonth() + 1;
        const day = now.getDate();
        dateBase = year + '/' + month + '/' + day + ' ';
      }
      if (Number(value)) {
        value = parseInt(value);
        dateBase = 0;
      }
      return this.createTimeStamp(dateBase + value);
    },

    /**
     * 解析默认值 value，字符串、时间戳
     * @param {Object} defaultTime
     */
    parseValue(value) {
      if (!value) {
        return;
      }
      if (this.type === 'time' && typeof value === 'string') {
        this.parseTimeType(value);
      } else {
        let defaultDate = null;
        defaultDate = new Date(value);
        if (this.type !== 'time') {
          this.year = defaultDate.getFullYear();
          this.month = defaultDate.getMonth() + 1;
          this.day = defaultDate.getDate();
        }
        if (this.type !== 'date') {
          this.hour = defaultDate.getHours();
          this.minute = defaultDate.getMinutes();
          this.second = defaultDate.getSeconds();
        }
      }
      if (this.hideSecond) {
        this.second = 0;
      }
    },

    /**
     * 解析可选择时间范围 start、end，年月日字符串、时间戳
     * @param {Object} defaultTime
     */
    parseDatetimeRange(point, pointType) {
      // 时间为空，则重置为初始值
      if (!point) {
        if (pointType === 'start') {
          this.startYear = 1920;
          this.startMonth = 1;
          this.startDay = 1;
          this.startHour = 0;
          this.startMinute = 0;
          this.startSecond = 0;
        }
        if (pointType === 'end') {
          this.endYear = 2120;
          this.endMonth = 12;
          this.endDay = 31;
          this.endHour = 23;
          this.endMinute = 59;
          this.endSecond = 59;
        }
        return;
      }
      if (this.type === 'time') {
        const pointArr = point.split(':');
        this[pointType + 'Hour'] = Number(pointArr[0]);
        this[pointType + 'Minute'] = Number(pointArr[1]);
        this[pointType + 'Second'] = Number(pointArr[2]);
      } else {
        if (!point) {
          pointType === 'start' ? (this.startYear = this.year - 60) : (this.endYear = this.year + 60);
          return;
        }
        if (Number(point)) {
          point = parseInt(point);
        }
        // datetime 的 end 没有时分秒, 则不限制
        const hasTime = /[0-9]:[0-9]/;
        if (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test(point)) {
          point = point + ' 23:59:59';
        }
        const pointDate = new Date(point);
        this[pointType + 'Year'] = pointDate.getFullYear();
        this[pointType + 'Month'] = pointDate.getMonth() + 1;
        this[pointType + 'Day'] = pointDate.getDate();
        if (this.type === 'datetime') {
          this[pointType + 'Hour'] = pointDate.getHours();
          this[pointType + 'Minute'] = pointDate.getMinutes();
          this[pointType + 'Second'] = pointDate.getSeconds();
        }
      }
    },

    // 获取 年、月、日、时、分、秒 当前可选范围
    getCurrentRange(value) {
      const range = [];
      for (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) {
        range.push(i);
      }
      return range;
    },

    // 字符串首字母大写
    capitalize(str) {
      return str.charAt(0).toUpperCase() + str.slice(1);
    },

    // 检查当前值是否在范围内，不在则当前值重置为可选范围第一项
    checkValue(name, value, values) {
      if (values.indexOf(value) === -1) {
        this[name] = values[0];
      }
    },

    // 每个月的实际天数
    daysInMonth(year, month) {
      // Use 1 for January, 2 for February, etc.
      return new Date(year, month, 0).getDate();
    },

    /**
     * 生成时间戳
     * @param {Object} time
     */
    createTimeStamp(time) {
      if (!time) return;
      if (typeof time === 'number') {
        return time;
      } else {
        time = time.replace(/-/g, '/');
        if (this.type === 'date') {
          time = time + ' ' + '00:00:00';
        }
        return Date.parse(time);
      }
    },

    /**
     * 生成日期或时间的字符串
     */
    createDomSting() {
      const yymmdd = this.year + '-' + this.lessThanTen(this.month) + '-' + this.lessThanTen(this.day);

      let hhmmss = this.lessThanTen(this.hour) + ':' + this.lessThanTen(this.minute);

      if (!this.hideSecond) {
        hhmmss = hhmmss + ':' + this.lessThanTen(this.second);
      }

      if (this.type === 'date') {
        return yymmdd;
      } else if (this.type === 'time') {
        return hhmmss;
      } else {
        return yymmdd + ' ' + hhmmss;
      }
    },

    /**
     * 初始化返回值，并抛出 change 事件
     */
    initTime(emit = true) {
      this.time = this.createDomSting();
      if (!emit) return;
      if (this.returnType === 'timestamp' && this.type !== 'time') {
        this.$emit('change', this.createTimeStamp(this.time));
        this.$emit('input', this.createTimeStamp(this.time));
        this.$emit('update:modelValue', this.createTimeStamp(this.time));
      } else {
        this.$emit('change', this.time);
        this.$emit('input', this.time);
        this.$emit('update:modelValue', this.time);
      }
    },

    /**
     * 用户选择日期或时间更新 data
     * @param {Object} e
     */
    bindDateChange(e) {
      const val = e.detail.value;
      this.year = this.years[val[0]];
      this.month = this.months[val[1]];
      this.day = this.days[val[2]];
    },
    bindTimeChange(e) {
      const val = e.detail.value;
      this.hour = this.hours[val[0]];
      this.minute = this.minutes[val[1]];
      this.second = this.seconds[val[2]];
    },

    /**
     * 初始化弹出层
     */
    initTimePicker() {
      if (this.disabled) return;
      const value = fixIosDateFormat(this.time);
      this.initPickerValue(value);
      this.visible = !this.visible;
    },

    /**
     * 触发或关闭弹框
     */
    tiggerTimePicker(e) {
      this.visible = !this.visible;
    },

    /**
     * 用户点击“清空”按钮，清空当前值
     */
    clearTime() {
      this.time = '';
      this.$emit('change', this.time);
      this.$emit('input', this.time);
      this.$emit('update:modelValue', this.time);
      this.tiggerTimePicker();
    },

    /**
     * 用户点击“确定”按钮
     */
    setTime() {
      this.initTime();
      this.tiggerTimePicker();
    }
  }
};
</script>
<style>
.uni-datetime-picker {
  /* #ifndef APP-NVUE */
  /* width: 100%; */
  /* #endif */
}

.uni-datetime-picker-view {
  height: 130px;
  width: 270px;
  /* #ifndef APP-NVUE */
  cursor: pointer;
  /* #endif */
}

.uni-datetime-picker-item {
  height: 50px;
  line-height: 50px;
  text-align: center;
  font-size: 14px;
}

.uni-datetime-picker-btn {
  margin-top: 60px;
  /* #ifndef APP-NVUE */
  display: flex;
  cursor: pointer;
  /* #endif */
  flex-direction: row;
  justify-content: space-between;
}

.uni-datetime-picker-btn-text {
  font-size: 14px;
  color: #007aff;
}

.uni-datetime-picker-btn-group {
  /* #ifndef APP-NVUE */
  display: flex;
  /* #endif */
  flex-direction: row;
}

.uni-datetime-picker-cancel {
  margin-right: 30px;
}

.uni-datetime-picker-mask {
  position: fixed;
  bottom: 0px;
  top: 0px;
  left: 0px;
  right: 0px;
  background-color: rgba(0, 0, 0, 0.4);
  transition-duration: 0.3s;
  z-index: 998;
}

.uni-datetime-picker-popup {
  border-radius: 8px;
  padding: 30px;
  width: 270px;
  /* #ifdef APP-NVUE */
  height: 500px;
  /* #endif */
  /* #ifdef APP-NVUE */
  width: 330px;
  /* #endif */
  background-color: #fff;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  transition-duration: 0.3s;
  z-index: 999;
}

.fix-nvue-height {
  /* #ifdef APP-NVUE */
  height: 330px;
  /* #endif */
}

.uni-datetime-picker-time {
  color: grey;
}

.uni-datetime-picker-column {
  height: 50px;
}

.uni-datetime-picker-timebox {
  border: 1px solid #e5e5e5;
  border-radius: 5px;
  padding: 7px 10px;
  /* #ifndef APP-NVUE */
  box-sizing: border-box;
  cursor: pointer;
  /* #endif */
}

.uni-datetime-picker-timebox-pointer {
  /* #ifndef APP-NVUE */
  cursor: pointer;
  /* #endif */
}

.uni-datetime-picker-disabled {
  opacity: 0.4;
  /* #ifdef H5 */
  cursor: not-allowed !important;
  /* #endif */
}

.uni-datetime-picker-text {
  font-size: 14px;
  line-height: 50px;
}

.uni-datetime-picker-sign {
  position: absolute;
  top: 53px;
  /* 减掉 10px 的元素高度，兼容nvue */
  color: #999;
  /* #ifdef APP-NVUE */
  font-size: 16px;
  /* #endif */
}

.sign-left {
  left: 86px;
}

.sign-right {
  right: 86px;
}

.sign-center {
  left: 135px;
}

.uni-datetime-picker__container-box {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 40px;
}

.time-hide-second {
  width: 180px;
}
</style>
