import Filter from 'bases/Filter';
import { getNesting, isObject, convertDate, dateAddDay, debounce } from 'includes/utility';

export default class DatePeriod extends Filter {
	name = 'date-period';
	datepickerButtonSelector = '.jet-date-period__datepicker-button';
	datepickerInputSelector = '.jet-date-period__datepicker-input';
	prevPeriodButtonSelector = '.jet-date-period__prev';
	nextPeriodButtonSelector = '.jet-date-period__next';
	datepickerOpenedClass = 'jet-date-period-datepicker-opened';
	periodIsSetClass = 'jet-date-period-is-set';
	periodStartClass = 'jet-date-period-start';
	periodSeparatorClass = 'jet-date-period-separator';
	periodEndClass = 'jet-date-period-end';

	constructor($container) {
		const $filter = $container.find('.jet-date-period');

		super($filter, $container);

		this.datePeriod = [];

		this.id = this.$filter.closest('.elementor-widget-jet-smart-filters-date-period').data('id');
		this.$datepickerBtn = $filter.find(this.datepickerButtonSelector);
		this.$prevPeriodBtn = $filter.find(this.prevPeriodButtonSelector);
		this.$nextPeriodBtn = $filter.find(this.nextPeriodButtonSelector);
		this.$datepickerInput = $filter.find(this.datepickerInputSelector);
		this.dateFormat = this.$datepickerInput.data('format');
		this.startEndDateEnabled = isObject(this.dateFormat) ? true : false;
		this.dateSeparator = this.startEndDateEnabled && this.dateFormat.separator ? ' ' + this.dateFormat.separator + ' ' : ' - ';
		this.periodType = this.$filter.data('period-type') || 'day';
		this.btnPlaceholder = this.$datepickerBtn.html();

		this.$datepickerInput.prop('type', 'text');

		this.debounceInitDatepickerWeekHover = debounce(this.initDatepickerWeekHover, 100);

		this.initDatepicker();
		this.initEvent();
		this.processData();
	}

	initDatepicker() {
		const datepickerOptions = {
			language: 'jsf',
			dateFormat: 'yy/m/d',
			autoClose: true,
			position: 'bottom left',
			offset: 0,
			view: 'days',
			minView: 'days',
			firstDay: Number(getNesting(JetSmartFilterSettings, 'misc', 'week_start'))
		}

		// add localization
		if (!$.fn.airDatepicker.language['jsf']) {
			const localizedText = getNesting(JetSmartFilterSettings, 'datePickerData');

			$.fn.airDatepicker.language['jsf'] = {
				days: localizedText.dayNames,
				daysShort: localizedText.dayNamesShort,
				daysMin: localizedText.dayNamesMin,
				months: localizedText.monthNames,
				monthsShort: localizedText.monthNamesShort,
				today: localizedText.currentText,
				clear: localizedText.closeText
			}
		}

		// datepicker on select
		datepickerOptions.onSelect = (formattedDate, date, inst) => {
			if (!date)
				return;

			if (this.periodType === 'week') {
				if (this.isDateInRange(date))
					return;

				// set week period on select
				const dayOffset = inst.opts.firstDay > date.getDay() ? inst.opts.firstDay - 7 : inst.opts.firstDay,
					startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + dayOffset),
					endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 6 + dayOffset);

				this.$datepickerInput.val(convertDate(startDate) + '-' + convertDate(endDate)).trigger('change');

				// select first day of the week
				inst.selectDate(startDate);
			} else if (this.periodType === 'month') {
				const startDate = date,
					endDate = new Date(date.getFullYear(), date.getMonth() + 1, 0);

				this.$datepickerInput.val(convertDate(startDate) + '-' + convertDate(endDate)).trigger('change');
			} else if (this.periodType === 'year') {
				const startDate = date,
					endDate = new Date(date.getFullYear(), 11, 31);

				this.$datepickerInput.val(convertDate(startDate) + '-' + convertDate(endDate)).trigger('change');
			} else if (this.periodType === 'range') {
				if (!Array.isArray(date) || date.length < 2)
					return;

				const startDate = date[0],
					endDate = date[1];

				this.$datepickerInput.val(convertDate(startDate) + '-' + convertDate(endDate)).trigger('change');
			} else {
				this.$datepickerInput.val(convertDate(date)).trigger('change');
			}
		};

		// datepicker on show
		datepickerOptions.onShow = inst => {
			if (this.id)
				inst.$datepicker.addClass('jet-date-period-' + this.id);

			this.$filter.addClass(this.datepickerOpenedClass);
			inst.$datepicker.addClass('jet-date-period-' + this.periodType);
		}

		// datepicker on hide
		datepickerOptions.onHide = inst => {
			if (this.id)
				inst.$datepicker.removeClass('jet-date-period-' + this.id);

			this.$filter.removeClass(this.datepickerOpenedClass);
			inst.$datepicker.removeClass('jet-date-period-' + this.periodType);
		};

		// datepicker on render cell
		datepickerOptions.onRenderCell = (date, cellType) => {
			// set active week period and add week hover
			if (this.periodType === 'week' && cellType === 'day') {
				this.debounceInitDatepickerWeekHover();

				if (this.isDateInRange(date)) {
					let classes = '-week-selected-';

					if (this.isDateFirstInRange(date))
						classes += ' -week-start-selected-';
					if (this.isDateLastInRange(date))
						classes += ' -week-end-selected-';

					return {
						classes
					}
				}
			}
		}

		if (this.periodType === 'month') {
			datepickerOptions.view = 'months';
			datepickerOptions.minView = 'months';
		}
		if (this.periodType === 'year') {
			datepickerOptions.view = 'years';
			datepickerOptions.minView = 'years';
		}

		if (this.periodType === 'range')
			datepickerOptions.range = true;

		// init air datepicker
		this.$datepicker = this.$datepickerInput.airDatepicker(datepickerOptions);
		this.datepicker = this.$datepicker.data('datepicker');

		// clear events to avoid duplication
		this.$datepickerBtn.off('click');
		this.$prevPeriodBtn.off('click');
		this.$nextPeriodBtn.off('click');
		this.$nextPeriodBtn.off('click');
		this.$datepickerInput.off('change');

		// init events
		this.$datepickerBtn.on('click', () => {
			this.datepicker.show();
		});

		this.$prevPeriodBtn.on('click', () => {
			this.prevPeriod();
		});

		this.$nextPeriodBtn.on('click', () => {
			this.nextPeriod();
		});

		this.$datepickerInput.on('change', () => {
			this.processData();
		});
	}

	addFilterChangeEvent() {
		this.$prevPeriodBtn.on('click', () => {
			this.emitFiterChange();
		});

		this.$nextPeriodBtn.on('click', () => {
			this.emitFiterChange();
		});

		this.$datepickerInput.on('change', () => {
			this.emitFiterChange();
		});
	}

	removeChangeEvent() {
		this.$datepickerBtn.off();
		this.$prevPeriodBtn.off();
		this.$nextPeriodBtn.off();
		this.$datepickerInput.off();
	}

	processData() {
		this.setPeriod();
		this.dataValue = this.$datepickerInput.val() || false;
	}

	setData(newData) {
		this.$datepickerInput.val(newData)
		this.processData();

		if (!this.datePeriod.length)
			return;

		const newDate = this.periodType === 'range' && this.datePeriod.length === 2
			? [this.datePeriod[0].date, this.datePeriod[1].date]
			: this.datePeriod[0].date;

		this.datepicker.selectDate(newDate);
	}

	reset() {
		this.$datepickerInput.val('');
		this.processData();
		this.datepicker.clear();
		this.datepicker.date = new Date();
	}

	get activeValue() {
		const periodStartDate = getNesting(this.datePeriod, '0', 'date'),
			periodStartFormatted = periodStartDate ? this.getFormattedDate(periodStartDate, 'start') : false,
			periodEndDate = this.startEndDateEnabled ? getNesting(this.datePeriod, '1', 'date') : false,
			periodEndFormatted = periodEndDate ? this.getFormattedDate(periodEndDate, 'end') : false;

		if (periodStartFormatted && periodEndFormatted) {
			return periodStartFormatted + this.dateSeparator + periodEndFormatted;
		} else {
			return periodStartFormatted;
		}
	}

	setPeriod() {
		const inputValue = this.$datepickerInput.val(),
			datesArray = [];

		if (inputValue)
			inputValue.split('-', 2).forEach(dateValue => {
				datesArray.push(dateValue);
			});

		this.datePeriod = [];

		datesArray.forEach(dateValue => {
			const date = new Date(dateValue.replaceAll('.', '/'));

			if (!(date instanceof Date))
				return;

			this.datePeriod.push({
				date,
				value: dateValue
			});
		});

		this.renderPeriod();
	}

	prevPeriod() {
		const periodStart = this.datePeriod[0] || false;

		if (!periodStart)
			return;

		const newPeriodEnd = dateAddDay(periodStart.date, -1),
			newPeriodEndValue = convertDate(newPeriodEnd);

		let newPeriodStart = false;

		if (this.periodType === 'week') {
			newPeriodStart = dateAddDay(newPeriodEnd, -6);
		} else if (this.periodType === 'month') {
			newPeriodStart = new Date(newPeriodEnd.getFullYear(), newPeriodEnd.getMonth(), 1);
		} else if (this.periodType === 'year') {
			newPeriodStart = new Date(newPeriodEnd.getFullYear(), 0, 1);
		} else if (this.periodType === 'range') {
			const oneDay = 24 * 60 * 60 * 1000,
				diffDays = Math.round(Math.abs((this.datePeriod[1].date - this.datePeriod[0].date) / oneDay));

			newPeriodStart = dateAddDay(new Date(newPeriodEnd.getTime()), - diffDays + 1);
		}

		const newPeriodStartValue = newPeriodStart ? convertDate(newPeriodStart) : '';

		this.$datepickerInput.val((newPeriodStartValue + '-' + newPeriodEndValue).replace(/^-|-$/g, '')).trigger('change');
		this.datepicker.selectDate(this.periodType === 'range' ? [newPeriodStart, newPeriodEnd] : newPeriodStart);
	}

	nextPeriod() {
		const periodEnd = this.datePeriod[1] || this.datePeriod[0] || false;

		if (!periodEnd)
			return;

		const newPeriodStart = dateAddDay(periodEnd.date),
			newPeriodStartValue = convertDate(newPeriodStart);

		let newPeriodEnd = false;

		if (this.periodType === 'week') {
			newPeriodEnd = dateAddDay(newPeriodStart, 6);
		} else if (this.periodType === 'month') {
			newPeriodEnd = new Date(newPeriodStart.getFullYear(), newPeriodStart.getMonth() + 1, 0);
		} else if (this.periodType === 'year') {
			newPeriodEnd = new Date(newPeriodStart.getFullYear(), 11, 31);
		} else if (this.periodType === 'range') {
			const oneDay = 24 * 60 * 60 * 1000,
				diffDays = Math.round(Math.abs((this.datePeriod[1].date - this.datePeriod[0].date) / oneDay));

			newPeriodEnd = dateAddDay(new Date(newPeriodStart.getTime()), diffDays - 1);
		}

		const newPeriodEndValue = newPeriodEnd ? convertDate(newPeriodEnd) : '';

		this.$datepickerInput.val((newPeriodStartValue + '-' + newPeriodEndValue).replace(/^:|:$/g, '')).trigger('change');
		this.datepicker.selectDate(this.periodType === 'range' ? [newPeriodStart, newPeriodEnd] : newPeriodStart);
	}

	renderPeriod() {
		if (!this.datePeriod.length) {
			this.$filter.removeClass(this.periodIsSetClass);
			this.$datepickerBtn.html(this.btnPlaceholder);

			return;
		}

		const periodStartDate = getNesting(this.datePeriod, '0', 'date'),
			periodStartHtml = periodStartDate ? `<div class="${this.periodStartClass}">${this.getFormattedDate(periodStartDate, 'start')}</div>` : '',
			periodEndDate = this.startEndDateEnabled ? getNesting(this.datePeriod, '1', 'date') : false,
			periodEndHtml = periodEndDate ? `<div class="${this.periodEndClass}">${this.getFormattedDate(periodEndDate, 'end')}</div>` : '',
			periodSeparatorHtml = periodStartDate && periodEndDate ? `<div class="${this.periodSeparatorClass}">${this.dateSeparator}</div>` : '';

		this.$filter.addClass(this.periodIsSetClass);
		this.$datepickerBtn.html(periodStartHtml + periodSeparatorHtml + periodEndHtml);
	}

	getFormattedDate(date, position = false) {
		let format = 'mm/dd/yy';

		if (this.dateFormat) {
			if (this.startEndDateEnabled) {
				if ((position === 'start' || !position) && this.dateFormat.start) format = this.dateFormat.start;
				if (position === 'end' && this.dateFormat.end) format = this.dateFormat.end;
			} else {
				format = this.dateFormat;
			}
		}

		return this.datepicker.formatDate(format, date);
	}

	isDateInRange(date) {
		if (!(date instanceof Date) || this.datePeriod.length < 2)
			return false;

		const datestamp = date.getTime(),
			startDatestamp = this.datePeriod[0].date.getTime(),
			endDatestamp = this.datePeriod[1].date.getTime();

		return datestamp >= startDatestamp && datestamp <= endDatestamp ? true : false;
	}

	isDateFirstInRange(date) {
		if (!(date instanceof Date) || !this.datePeriod[0])
			return false;

		const datestamp = date.getTime(),
			startDatestamp = this.datePeriod[0].date.getTime();

		return datestamp === startDatestamp ? true : false;
	}

	isDateLastInRange(date) {
		if (!(date instanceof Date) || !this.datePeriod[1])
			return false;

		const datestamp = date.getTime(),
			endDatestamp = this.datePeriod[1].date.getTime();

		return datestamp === endDatestamp ? true : false;
	}

	initDatepickerWeekHover() {
		const $days = this.datepicker.$content.find('.datepicker--cells-days .datepicker--cell-day');
		let weekHoveredDays = [];

		$days.off().on({
			mouseenter: evt => {
				const dayNumber = [...evt.target.parentNode.children].indexOf(evt.target);

				if (dayNumber < 0)
					return;

				for (let index = 0; index < 7; index++) {
					const $day = $days.eq(index + Math.floor(dayNumber / 7) * 7);

					$day.addClass('-week-hover-');
					if (index === 0) $day.addClass('-week-start-hover-');
					if (index === 6) $day.addClass('-week-end-hover-');

					weekHoveredDays.push($day);
				}
			},
			mouseleave: evt => {
				weekHoveredDays.forEach($day => {
					$day.removeClass('-week-hover- -week-start-hover- -week-end-hover-');
				});

				weekHoveredDays = [];
			}
		});
	}
}