<template>
	<div class="date-picker">
		<div class="date-picker__header">
			<w-button
				lg
				text
				color="negative-4"
				icon="mdi mdi-chevron-left"
				@click="moveViewer(-1)"
			/>
			<div class="date-picker__header-title" @click="showMonthPicker = !showMonthPicker">
				<transition
					:name="transitionName"
					mode="out-in"
				>
					<div :key="yearDisplay" class="date-picker__header-year">
						{{ yearDisplay }}
					</div>
				</transition>
				<transition
					:name="transitionName"
					mode="out-in"
				>
					<div :key="monthDisplay" class="date-picker__header-month">
						{{ monthDisplay }}
					</div>
				</transition>
			</div>
			<w-button
				lg
				text
				color="negative-4"
				icon="mdi mdi-chevron-right"
				@click="moveViewer(1)"
			/>
		</div>
		
		<div class="date-picker__schedule">
			<w-transition-slide-fade down>
				<div
					v-if="showMonthPicker"
					class="date-picker__month-picker"
				>
					<div
						v-for="(id, i) in monthIds"
						:key="i"
						class="date-picker__month-cell"
						:class="computeMonthClass(i)"
						@click="selectMonth(i)"
					>
						{{ id }}
					</div>
				</div>
			</w-transition-slide-fade>
			<div
				v-for="(id, i) in weekIds"
				:key="i"
				class="date-picker__week"
			>
				{{ id }}
			</div>
			<div
				v-for="day in daysInMonth"
				:key="day.id"
				role="button"
				tabindex="0"
				class="date-picker__day-cell"
				:class="computeDayClass(day)"
				@click="selectDay(day)"
			>
				{{ day.date }}
			</div>
		</div>

		<div v-if="showTime" class="date-picker__time">
			<w-icon>
				mdi mdi-clock-outline
			</w-icon>
			<w-select
				v-model="hourValue"
				:items="hourValues"
				:menu-props="{ zIndex: 600 }"
				:disabled="!selected.length"
				menu-class="date-picker__dropdown"
				bg-color="transparent"
				no-unselect
				@wheel.native="onScrollHour"
			/>
			<strong>:</strong>
			<w-select
				v-model="minuteValue"
				:items="minuteValues"
				:menu-props="{ zIndex: 600 }"
				:disabled="!selected.length"
				menu-class="date-picker__dropdown"
				bg-color="transparent"
				no-unselect
				@wheel.native="onScrollMinute"
			/>
		</div>
	</div>
</template>

<script lang="ts">
import dayjs, { Dayjs } from 'dayjs';
import { isEqual } from 'lodash';
import Vue from 'vue';

type PickerMode = 'SINGLE' | 'MULTI' | 'RANGE';

interface PickerDay {
	id: number;
	date: number;
	current: boolean;
	outside: boolean;
	value: number;
	selected: boolean;
	inRange: boolean;
}

export default Vue.extend({
	
	props: {
		value: [ Number, Array ],
		range: Boolean,
		multiple: Boolean,
		showTime: [ Boolean, String ]
	},

	data: () => ({
		viewer: dayjs(),
		selected: [] as number[],
		showMonthPicker: false,
		transitionName: 'slide-swap-left',
		weekIds: [ 'S', 'M', 'T', 'W', 'T', 'F', 'S' ],
		monthIds: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
	}),

	computed: {
		pickerMode(): PickerMode {
			return this.range ? 'RANGE' : this.multiple ? 'MULTI' : 'SINGLE';
		},

		yearDisplay(): number {
			return this.viewer.year();
		},

		monthDisplay(): string {
			return this.viewer.format('MMMM');
		},

		daysInMonth(): PickerDay[] {
			const days: PickerDay[] = [];
			const today = dayjs().startOf('day');
			const startDay = this.viewer.startOf('month').startOf('day');
			const endDay = this.viewer.endOf('month').startOf('day');
			const checkRange = this.pickerMode == 'RANGE' && this.selected.length >= 2;

			let initial = startDay;
			let final = endDay;

			// Add week start padding
			while(initial.day() > 0) {
				initial = initial.subtract(1, 'day');
			}

			// Add week end padding
			while(final.day() != 0) {
				final = final.add(1, 'day');
			}

			// Iterate days in month
			for(let curr = initial; curr.isBefore(final); curr = curr.add(1, 'day')) {
				const currValue = curr.valueOf();
				
				days.push({
					id: currValue * this.viewer.valueOf(),
					date: curr.date(),
					current: curr.isSame(today),
					outside: curr.isBefore(startDay) || curr.isAfter(endDay),
					value: currValue,
					selected: this.selected.some(sel => sel == currValue),
					inRange: checkRange && this.isDayInRange(curr)
				});
			}

			return days;
		},

		hourValues(): any {
			return Array.from(Array(24).keys()).map(i => {
				const num = i.toString();

				return {
					label: num.padStart(2, '0'),
					value: num.toString()
				};
			});
		},

		minuteValues(): any {
			return Array.from(Array(60).keys()).map(i => {
				const num = i.toString();

				return {
					label: num.padStart(2, '0'),
					value: num.toString()
				};
			});
		},

		hourValue: {
			get(): string {
				if(!this.selected.length) {
					return '0';
				}

				return dayjs(this.selected[0]).hour().toString();
			},
			set(value: string) {
				if(!this.selected.length) {
					return;
				}

				// console.log(this.selected[0].toString());
				// console.log('new hour value: ' + value);

				this.selected[0] = dayjs(this.selected[0]).hour(parseInt(value)).valueOf();

				// console.log(this.selected[0].toString());

				this.$emit('input', this.selected[0]);
			}
		},

		minuteValue: {
			get(): string {
				if(!this.selected.length) {
					return '0';
				}

				return dayjs(this.selected[0]).minute().toString();
			},
			set(value: string) {
				if(!this.selected.length) {
					return;
				}

				this.selected[0] = dayjs(this.selected[0]).minute(parseInt(value)).valueOf();
				this.$emit('input', this.selected[0]);
			}
		}
	},

	watch: {
		value(value) {
			this.parseValue(value);
		},

		viewer(value, old) {
			this.transitionName = old.isBefore(value)
				? 'date-picker__slide-left'
				: 'date-picker__slide-right';
		},

		selected(value) {
			const mapped = value.map((day: Dayjs) => day.valueOf());

			if(this.pickerMode == 'SINGLE') {
				this.$emit('input', mapped[0]);
			} else {
				this.$emit('input', mapped);
			}
		}
	},

	created() {
		if(this.value) {
			this.parseValue(this.value);
		}
	},

	methods: {
		parseValue(value: any) {
			if(!value) {
				this.selected = [];
				return;
			}

			let selection = [];

			if(this.pickerMode == 'SINGLE') {
				if(typeof value != 'number') {
					throw new Error('Expected date input as number ' + value);
				}

				selection = [ dayjs(value) ];
			} else if(this.pickerMode == 'MULTI') {
				if(typeof value != 'object') {
					throw new Error('Expected date input as array');
				}

				selection = value.map((v: number) => dayjs(v));
			} else if(this.pickerMode == 'RANGE') {
				if(typeof value != 'object') {
					throw new Error('Expected date input as array');
				}

				for(let i = 0; i < 2 && i < value.length; i++) {
					selection.push(dayjs(value[i]));
				}
			}

			if(isEqual(this.selected, selection)) {
				return;
			}

			this.selected = selection;

			if(this.selected.length > 0) {
				this.viewer = dayjs(this.selected[0]);
			}
		},

		moveViewer(delta: number) {
			this.viewer = this.viewer.add(delta, 'month');
		},

		computeDayClass(day: PickerDay): any {
			return {
				'date-picker__day-cell--current': day.current,
				'date-picker__day-cell--outside': day.outside,
				'date-picker__day-cell--selected': day.selected,
				'date-picker__day-cell--in-range': day.inRange
			};
		},

		computeMonthClass(month: number): any {
			return {
				'date-picker__month-cell--current': month == this.viewer.month()
			};
		},

		selectDay(day: PickerDay) {
			if(this.pickerMode == 'SINGLE') {
				this.selected = [ day.value ];
			} else if(this.pickerMode == 'MULTI') {
				const index = this.selected.findIndex(sel => sel == day.value);

				if(index > -1) {
					this.selected.splice(index, 1);
				} else {
					this.selected.push(day.value);
				}
			} else if(this.pickerMode == 'RANGE') {
				const index = this.selected.findIndex(sel => sel == day.value);

				if(index >= 0) {
					return;
				}

				if(this.selected.length >= 2) {
					this.selected = [];
				}

				this.selected.push(day.value);
			}
		},

		selectMonth(month: number) {
			this.viewer = this.viewer.set('month', month);
			this.showMonthPicker = false;
		},

		isDayInRange(curr: Dayjs) {
			const rangeFirst = dayjs(this.selected[0]);
			const rangeSecond = dayjs(this.selected[1]);

			const rangeStart = dayjs.min(rangeFirst, rangeSecond);
			const rangeEnd = dayjs.max(rangeFirst, rangeSecond);

			return rangeStart.valueOf() < curr.valueOf() && curr.valueOf() < rangeEnd.valueOf();
		},

		modulo(n: number, m: number) {
			return (n % m + m) % m;
		},

		onScrollHour(event: any) {
			const current = parseInt(this.hourValue);
			const delta = event.deltaY < 0 ? 1 : -1;
			const newHour = this.modulo(current + delta, 24).toString();
			
			this.hourValue = newHour;
		},

		onScrollMinute(event: any) {
			const current = parseInt(this.minuteValue);
			const delta = event.deltaY < 0 ? 1 : -1;

			this.minuteValue = this.modulo(current + delta, 60).toString();
		},
	}
});
</script>

<style lang="scss">
.date-picker {
	@include rounded(lg);

	background-color: #f2f4f8;
	border: 1px solid #cbd5e0;
	position: relative;
	overflow: hidden;

	@include dark {
		border: 1px solid #494c50;
	}

	// .w-divider {
	// 	border-color: #cbd5e0;
	// }

	&__header {
		@include padding(1);
		@include padding(3, b);

		display: flex;
		align-items: center;
		background-color: var(--color-shade-0);
	}

	&__header-title {
		flex: 1;
		text-align: center;
		position: relative;
	}

	&__header-month {
		@include font-weight(bold);

		line-height: 1rem;
		font-size: 1.05rem;
		top: 0rem;
	}

	&__header-year {
		@include font-weight(semibold);
		@include font-size(xs);

		color: var(--color-negative-4);
		top: -1rem;
	}

	&__header-month,
	&__header-year {
		position: absolute;
		cursor: pointer;
		left: 0;
		right: 0;
		margin: 0 auto;
	}

	&__schedule {
		@include padding(1);
		@include padding(2, t);

		display: grid;
		grid-template-columns: repeat(7, minmax(0, 1fr));
		// grid-template-rows: repeat(7, minmax(0, 1fr));
		gap: 0.25rem;
	}

	&__week {
		@include font-weight(semibold);
		@include font-size(xs);

		color: #a0aec0;
		text-align: center;
	}

	&__day-cell {
		@include rounded(full);
		@include transition(colors);
		@include font-size(xs);
		@include font-weight(medium);
		@include padding(1.25);

		aspect-ratio: 1/1;
		display: flex;
		user-select: none;
		align-items: center;
		justify-content: center;
		cursor: pointer;
		color: #464758;

		@include dark {
			color: white;
		}
		
		&:hover {
			background-color: var(--color-shade-2);
		}

		&--in-range:not(&--outside) {
			$color: map-get($theme-general, 'primary');

			background-color: rgba($color, 0.3);
		}

		&--current, &--current:hover {
			color: var(--color-primary);
		}

		&--selected, &--selected:hover {
			background-color: var(--color-primary);
			color: white;
			cursor: unset;
		}

		&--outside, &--outside:hover {
			pointer-events: none;
			opacity: 0.25;
		}
	}

	&__month-picker {
		@include padding(1);

		background-color: var(--color-shade-0);
		position: absolute;
		inset: 0;
		top: 48px;
		z-index: 2;
		display: grid;
		grid-template-columns: repeat(3, minmax(0, 1fr));
		animation-duration: 250ms !important;
	}

	&__month-cell {
		@include rounded(lg);

		display: flex;
		align-items: center;
		justify-content: center;
		cursor: pointer;

		&:hover {
			background-color: rgba(0, 0, 0, 0.1);
		}

		&--current {
			color: var(--color-primary);
		}
	}

	&__time {
		@include padding(2);

		background-color: var(--color-shade-0);
		display: flex;
		align-items: center;
		gap: 0.15rem;

		> strong {
			@include font-size(lg);
			@include margin(-0.5, t);
		}

		.w-select {
			max-width: 24px;

			&__selection-wrap {
				padding: 0;
			}
		
			input {
				@include font-size(lg);
				
				padding: 0;
				padding-left: 2px;
			}

			label {
				display: none;
			}
		}
	}

	&__dropdown {
		max-height: 10rem;

		.w-list__item-label {
			@include padding(1, y);
		}
	}

	&__slide {
		&-left-enter-to,
		&-right-enter-to,
		&-left-leave,
		&-right-leave { transform: translateX(0); opacity: 1; }

		&-left-enter,
		&-right-leave-to { transform: translateX(0.5rem); opacity: 0; }

		&-left-leave-to,
		&-right-enter { transform: translateX(-0.5rem); opacity: 0; }

		&-left-enter-active,
		&-left-leave-active,
		&-right-enter-active,
		&-right-leave-active { transition: all 150ms ease-in-out }
	}
}
</style>