<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,
			}
		},
		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();
			},

			//兼容 iOS、safari 日期格式
			fixIosDateFormat(value) {
				if (typeof value === 'string') {
					value = value.replace(/-/g, '/')
				}
				return value
			},

			/**
			 * 生成时间戳
			 * @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 lang="scss">
	$uni-primary: #007aff !default;

	.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: $uni-primary;
	}

	.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>
