import { IScheduleSettings, ITimingRule } from '@core/types'
import { addDays, addMinutes, isAfter, set } from 'date-fns'
import { range } from 'es-toolkit/compat'
import { DateUtils } from './date.utils'
import { getDaySchedule } from './schedule'
import { TimeUtils } from './time.utils'

/**
 * Generates a list of time slots between the given start and end date
 * @param start The start date of the time slots
 * @param end The end date of the time slots
 * @param slotLength The length of each time slot in minutes
 * @returns A list of time slots
 */
export function getTimeSlotsBetweenTime(start: Date, end: Date, slotLength: number): Date[] {
	const timeSlots: Date[] = []

	// Generate time slots of slotLength minutes until endTime is greater than startTime
	while (isAfter(end, start)) {
		// TODO: Finalize this behavior with Alex and Kais
		// If start + slotLength exceed endTime, skip it
		// meaning if a person reserved at 09:00 with slotLength of 30 but restaurant closes at 09:15,
		// we stop adding timeslots
		if (isAfter(addMinutes(start, slotLength), end)) break

		timeSlots.push(DateUtils.normalizeSeconds(start))

		start = addMinutes(start, slotLength)
	}

	return timeSlots
}

/**
 * Generates a list of time slots between the given date and rules
 * @param date The date for which time slots will be generated
 * @param rules A list of {@link ITimingRule} to calculate time slots
 * @param slotLength The length of each time slot in minutes
 * @returns A list of time slots in the given date
 */
export function getTimeSlotsByDate(date: Date, rules: ITimingRule[], slotLength: number): Date[] {
	const timeSlots: Date[] = []

	for (const rule of rules) {
		// Set date like 2025-01-01T10:30:00.000
		const startTime = DateUtils.normalizeSeconds(set(date, { ...rule.openingTime }))
		let endTime = DateUtils.normalizeSeconds(set(date, { ...rule.closingTime }))

		// If closingTime is less than opening time, this means shift ends in next day
		// So we add a day to endTime
		if (TimeUtils.isBefore(rule.closingTime, rule.openingTime)) {
			endTime = addDays(endTime, 1)
		}

		timeSlots.push(...getTimeSlotsBetweenTime(startTime, endTime, slotLength))
	}

	return timeSlots
}

/**
 * Get timeslots for entire week starting from the weekStart date
 * @param weekStart Start date of week
 * @param schedule Restaurant schedule
 * @param slotLength The length of each time slot in minutes
 * @returns Array of time slots for entire week
 */
export function getTimeSlotsForWeek(weekStart: Date, schedule: IScheduleSettings, slotLength: number): Date[] {
	const timeSlots: Date[] = []

	for (const weekDay of range(0, 7)) {
		const currentDay = addDays(weekStart, weekDay)

		const daySchedule = getDaySchedule(currentDay, schedule)

		if (daySchedule.closed || daySchedule.rules.length === 0) {
			continue
		}

		timeSlots.push(...getTimeSlotsByDate(currentDay, daySchedule.rules, slotLength))
	}

	return timeSlots
}
