import { WeekDay } from '@core/types'
import { addDays, isAfter as fnsIsAfter, isBefore as fnsIsBefore, isEqual as fnsIsEqual, set } from 'date-fns'

const iso8601Regex =
	/^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])(?:[\sT](?:0\d|1\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:[Zz]|[+-](?:0\d|1\d|2[0-3]):[0-5]\d)?)?$/

/**
 * Check whether a date comes before another date
 * @param leftDate first date
 * @param rightDate second date
 * @returns true if leftDate comes before rightDate and vice versa
 */
function isBefore(leftDate: Date, rightDate: Date): boolean {
	const left = normalizeDay(leftDate)
	const right = normalizeDay(rightDate)
	return !fnsIsEqual(left, right) && fnsIsBefore(left, right)
}

/**
 * Check whether a date comes after another date
 * @param leftDate first date
 * @param rightDate second date
 * @returns true if leftDate comes after rightDate and vice versa
 */
function isAfter(leftDate: Date, rightDate: Date): boolean {
	const left = normalizeDay(leftDate)
	const right = normalizeDay(rightDate)
	return !fnsIsEqual(left, right) && fnsIsAfter(left, right)
}

/**
 * Check whether two dates are equal
 * @param leftDate first date
 * @param rightDate: second date
 * @returns true if leftDate equals rightDate and vice versa
 */
function isEqual(leftDate: Date, rightDate: Date): boolean {
	const left = normalizeDay(leftDate)
	const right = normalizeDay(rightDate)
	return fnsIsEqual(left, right)
}

/**
 * normalize a date by setting its time to 00:00
 * @param date the date to normalize
 * @returns Normalized date
 */
function normalizeDay(date: Date): Date {
	return set(date, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 })
}

/**
 * normalize a date by setting its time to 23:59
 * @param date the date to normalize
 * @returns Normalized date
 */
function normalizeDayEnd(date: Date): Date {
	return set(date, { hours: 23, minutes: 59, seconds: 59, milliseconds: 59 })
}

/**
 * normalize a date by setting seconds and milliseconds to 0
 * @param date the date to normalize
 * @returns normalized date
 */
function normalizeSeconds(date: Date): Date {
	return set(date, { seconds: 0, milliseconds: 0 })
}

function getWeekDay(date: Date): WeekDay {
	return date.toLocaleDateString('en-Us', { weekday: 'long' }) as WeekDay
}

function rangeDates(start: Date, end: Date) {
	const dateArray: Date[] = []
	let currentDate = start
	while (currentDate <= end) {
		dateArray.push(currentDate)
		currentDate = addDays(currentDate, 1)
	}
	return dateArray
}

/**
 * validate if the given string is a valid iso8601 date string
 * @param date The date string to validate
 * @returns true is given date is a valid iso8601 string
 */
function isIsoDateString(date: string): boolean {
	if (typeof date === 'string') {
		return iso8601Regex.test(date)
	}

	return false
}

export const DateUtils = {
	isBefore,
	isAfter,
	isEqual,
	normalizeDay,
	normalizeDayEnd,
	normalizeSeconds,
	getWeekDay,
	rangeDates,
	isIsoDateString,
}
