<script setup lang="ts">
import type { MaybeRefOrGetter } from 'vue'
import type { ModeType } from '../../../utils/calendar/typings'
import { Btn, NumberInput } from '@bagelink/vue'
import { computed, ref, toValue } from 'vue'
import Time, { WEEK_START_DAY } from '../../../utils/calendar/time'

const props = withDefaults(
	defineProps<{
		modelValue?: string | Date
		min?: string | Date
		max?: string | Date
		mode?: ModeType
		firstDayOfWeek?: WEEK_START_DAY
		locale?: string
		enableTime?: boolean
		highlightedDates?: MaybeRefOrGetter<(string | Date)[]>
		disabledDates?: MaybeRefOrGetter<(string | Date)[]>
		allowedDates?: MaybeRefOrGetter<(string | Date)[]>
		autoSize?: boolean
	}>(),
	{
		mode: () => ({ mode: 'day' }),
		firstDayOfWeek: WEEK_START_DAY.SUNDAY,
		locale: '',
		enableTime: false,
	},
)

const emit = defineEmits(['update:modelValue'])

const computedHighlightedDates = computed(() => toValue(props.highlightedDates))
const computedDisabledDates = computed(() => toValue(props.disabledDates))
const computedAllowedDates = computed(() => toValue(props.allowedDates))

// State
const currentMonth = ref(new Date())
const currentView = ref('days')

// Time helper instance
const time = new Time(props.firstDayOfWeek, props.locale)

// Parse a date string or object into a Date object
function parseDate(value: string | Date | undefined): Date | null {
	if (!value) { return null }
	const date = typeof value === 'string' ? new Date(value) : value
	return Number.isNaN(date.getTime()) ? null : date
}

// Date validation composable
function useDateValidation() {
	const isDateDisabled = (date: Date | null) => {
		if (!date) { return true }
		const minDate = parseDate(props.min)
		const maxDate = parseDate(props.max)

		// Check if date is in min/max range
		if (minDate && date < minDate) { return true }
		if (maxDate && date > maxDate) { return true }

		// Check if date is in the disabled dates array
		if (Array.isArray(computedDisabledDates.value) && computedDisabledDates.value.length > 0) {
			const isInDisabledDates = computedDisabledDates.value.some((disabledDate) => {
				const parsedDisabledDate = parseDate(disabledDate)
				if (!parsedDisabledDate) { return false }

				// Compare year, month, and day
				return date.getFullYear() === parsedDisabledDate.getFullYear()
					&& date.getMonth() === parsedDisabledDate.getMonth()
					&& date.getDate() === parsedDisabledDate.getDate()
			})
			if (isInDisabledDates) { return true }
		}

		// If allowedDates is set, only those dates are enabled
		if (Array.isArray(computedAllowedDates.value) && computedAllowedDates.value.length > 0) {
			const dateKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
			const isAllowed = computedAllowedDates.value.some((allowedDate) => {
				if (typeof allowedDate === 'string') {
					// Direct string comparison (YYYY-MM-DD)
					return allowedDate === dateKey
				}
				return allowedDate.getFullYear() === date.getFullYear()
					&& allowedDate.getMonth() === date.getMonth()
					&& allowedDate.getDate() === date.getDate()
			})
			if (!isAllowed) { return true }
		}

		return false
	}

	const isMonthDisabled = (year: number, monthIndex: number) => {
		const minDate = parseDate(props.min)
		const maxDate = parseDate(props.max)

		// Disable only if every day in the month is out of range
		const lastDayOfMonth = new Date(year, monthIndex + 1, 0)
		const firstDayOfMonth = new Date(year, monthIndex, 1)

		if (minDate) {
			const minMidnight = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate())
			if (lastDayOfMonth < minMidnight) { return true }
		}
		if (maxDate) {
			const maxMidnight = new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate())
			if (firstDayOfMonth > maxMidnight) { return true }
		}
		return false
	}

	const isYearDisabled = (year: number) => {
		const minDate = parseDate(props.min)
		const maxDate = parseDate(props.max)

		if (minDate && year < minDate.getFullYear()) { return true }
		if (maxDate && year > maxDate.getFullYear()) { return true }
		return false
	}

	return {
		isDateDisabled,
		isMonthDisabled,
		isYearDisabled
	}
}

// Date state composable
function useDateState() {
	const selectedDate = computed(() => {
		return parseDate(props.modelValue)
	})

	return {
		selectedDate
	}
}

// Initialize core composables
const { isDateDisabled, isMonthDisabled, isYearDisabled } = useDateValidation()
const { selectedDate } = useDateState()

// Calendar view composable
function useCalendarView() {
	const currentMonthDays = computed(() => {
		const year = currentMonth.value.getFullYear()
		const month = currentMonth.value.getMonth()

		// Get the weeks for the current month
		const weeksInMonth = time.getCalendarMonthSplitInWeeks(year, month)

		// Always ensure we have 6 rows (42 days) in our calendar
		// If we have less than 6 weeks, add extra week(s) from the next month
		while (weeksInMonth.length < 6) {
			const lastWeek = weeksInMonth[weeksInMonth.length - 1]
			const lastDay = lastWeek[lastWeek.length - 1]
			const nextDay = new Date(lastDay.getFullYear(), lastDay.getMonth(), lastDay.getDate() + 1)
			const nextWeek = time.getCalendarWeekDateObjects(nextDay)
			weeksInMonth.push(nextWeek)
		}

		return weeksInMonth.flat()
	})

	const currentMonthValue = computed(() => ({
		month: currentMonth.value.getMonth(),
		year: currentMonth.value.getFullYear(),
		formatted: {
			month: time.getLocalizedNameOfMonth(currentMonth.value, 'long'),
			year: time.getLocalizedDateString(currentMonth.value).split('/').pop()
		}
	}))

	const months = computed(() => Array.from({ length: 12 }, (_, i) => {
		const date = new Date(currentMonthValue.value.year, i, 1)
		return {
			name: time.getLocalizedNameOfMonth(date, 'short'),
			value: i,
			disabled: isMonthDisabled(currentMonthValue.value.year, i)
		}
	}))

	const years = computed(() => {
		const startYear = currentMonthValue.value.year - 10
		return Array.from({ length: 18 }, (_, i) => ({
			value: startYear + i,
			disabled: isYearDisabled(startYear + i)
		}))
	})

	const weekDays = computed(() => {
		const weekStart = new Date()
		weekStart.setDate(weekStart.getDate() - weekStart.getDay()
			+ (props.firstDayOfWeek === WEEK_START_DAY.MONDAY ? 1 : 0))
		return Array.from({ length: 7 }, (_, i) => {
			const day = new Date(weekStart)
			day.setDate(weekStart.getDate() + i)
			return time.getLocalizedNameOfWeekday(day, 'short')
		})
	})

	const isSelected = (date: Date | null) => {
		if (!date || !selectedDate.value) { return false }
		return date.getFullYear() === selectedDate.value.getFullYear()
			&& date.getMonth() === selectedDate.value.getMonth()
			&& date.getDate() === selectedDate.value.getDate()
	}

	const isToday = (date: Date | null) => {
		if (!date) { return false }
		return time.dateIsToday(date)
	}

	const isNotInMonth = (date: Date) => {
		return time.isTrailingOrLeadingDate(date, currentMonth.value.getMonth())
	}

	/**
	 * Determines if a date should be highlighted based on the highlightedDates prop
	 * @param date The date to check
	 * @returns True if the date should be highlighted
	 */
	const isHighlighted = computed(() => (date?: Date): boolean => {
		if (!date || !Array.isArray(computedHighlightedDates.value) || !computedHighlightedDates.value.length) {
			return false
		}

		return computedHighlightedDates.value.some((highlightedDate) => {
			const parsedDate = parseDate(highlightedDate)
			if (!parsedDate) { return false }

			// Compare year, month, and day
			return date.getFullYear() === parsedDate.getFullYear()
				&& date.getMonth() === parsedDate.getMonth()
				&& date.getDate() === parsedDate.getDate()
		})
	})

	return {
		currentMonthDays,
		currentMonthValue,
		months,
		years,
		weekDays,
		isSelected,
		isToday,
		isNotInMonth,
		isHighlighted
	}
}

// Navigation handlers
function useNavigation() {
	const selectMonth = (monthIndex: number) => {
		currentMonth.value = new Date(currentMonth.value.getFullYear(), monthIndex, 1)
		currentView.value = 'days'
	}

	const selectYear = (year: number) => {
		currentMonth.value = new Date(year, currentMonth.value.getMonth(), 1)
		currentView.value = 'months'
	}

	const previousMonth = () => {
		currentMonth.value = new Date(currentMonth.value.getFullYear(), currentMonth.value.getMonth() - 1, 1)
	}

	const nextMonth = () => {
		currentMonth.value = new Date(currentMonth.value.getFullYear(), currentMonth.value.getMonth() + 1, 1)
	}

	const previousYear = () => {
		const offset = currentView.value === 'months' ? 1 : 21
		currentMonth.value = new Date(currentMonth.value.getFullYear() - offset, currentMonth.value.getMonth(), 1)
	}

	const nextYear = () => {
		const offset = currentView.value === 'months' ? 1 : 21
		currentMonth.value = new Date(currentMonth.value.getFullYear() + offset, currentMonth.value.getMonth(), 1)
	}

	return {
		selectMonth,
		selectYear,
		previousMonth,
		nextMonth,
		previousYear,
		nextYear
	}
}

// Time handling composable
function useTimeHandling() {
	const hours = computed(() => selectedDate.value?.getHours() ?? 0)
	const minutes = computed(() => selectedDate.value?.getMinutes() ?? 0)

	const handleHourInput = (value: number) => {
		if (!selectedDate.value) { return }
		const newDate = new Date(selectedDate.value)
		newDate.setHours(value)
		emitDateValue(newDate)
	}

	const handleMinuteInput = (value: number) => {
		if (!selectedDate.value) { return }
		const newDate = new Date(selectedDate.value)
		newDate.setMinutes(value)
		emitDateValue(newDate)
	}

	return {
		hours,
		minutes,
		handleHourInput,
		handleMinuteInput
	}
}

// Centralized date emission function
function emitDateValue(date: Date) {
	if (props.enableTime) {
		emit('update:modelValue', date.toISOString())
	} else {
		// For date-only, set time to midnight
		const midnightDate = new Date(date)
		midnightDate.setHours(0, 0, 0, 0)
		emit('update:modelValue', midnightDate.toISOString())
	}
}

// Initialize remaining composables
const {
	currentMonthDays,
	currentMonthValue,
	months,
	years,
	weekDays,
	isSelected,
	isToday,
	isNotInMonth,
	isHighlighted
} = useCalendarView()
const { selectMonth, selectYear, previousMonth, nextMonth, previousYear, nextYear } = useNavigation()
const { hours, minutes, handleHourInput, handleMinuteInput } = useTimeHandling()

// Date selection handler
function selectDate(date: Date | null) {
	if (!date) { return }

	if (props.enableTime) {
		// Create date with current time values
		const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
		const currentHours = selectedDate.value?.getHours() ?? new Date().getHours()
		const currentMinutes = selectedDate.value?.getMinutes() ?? new Date().getMinutes()
		newDate.setHours(currentHours)
		newDate.setMinutes(currentMinutes)

		emitDateValue(newDate)
	} else {
		// For date-only selection, create a date at midnight
		const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
		emitDateValue(newDate)
	}
}
</script>

<template>
	<div
		class="ltr gap-075 m_flex-wrap calendar-container justify-content-center h-100p border p-025"
		:class="{ flex: !autoSize }"
	>
		<div class="calendar-section m_border-none px-05 pb-05 pt-025 m_p-0">
			<div class="flex space-between pb-1">
				<template v-if="currentView === 'days'">
					<Btn flat thin icon="chevron_left" @click="previousMonth" />
					<div class="flex gap-05">
						<Btn flat thin class="month-btn" @click="currentView = 'months'">
							{{ currentMonthValue.formatted.month }}
						</Btn>
						<Btn flat thin class="year-btn" @click="currentView = 'years'">
							{{ currentMonthValue.formatted.year }}
						</Btn>
					</div>
					<Btn flat thin icon="chevron_right" @click="nextMonth" />
				</template>
				<template v-else>
					<Btn flat thin icon="chevron_left" @click="previousYear" />
					<span class="month-year">{{ currentMonthValue.formatted.year }}</span>
					<Btn flat thin icon="chevron_right" @click="nextYear" />
				</template>
			</div>

			<div v-if="currentView === 'days'" class="calendar-grid grid gap-025">
				<div v-for="day in weekDays" :key="day" class="txt-center txt-12 opacity-6">
					{{ day }}
				</div>

				<button
					v-for="date in currentMonthDays" :key="date?.toISOString()" type="button"
					class="day aspect-ratio-1 flex align-items-center justify-content-center pointer round txt14 p-0"
					:class="{
						'selected': isSelected(date),
						'today': isToday(date),
						'disabled': isDateDisabled(date),
						'not-in-month': isNotInMonth(date),
						'highlighted': isHighlighted(date),
					}" :disabled="isDateDisabled(date)" @click="selectDate(date)"
				>
					{{ date?.getDate() }}
				</button>
			</div>

			<div v-else-if="currentView === 'months'" class="month-grid grid gap-05 p-05">
				<Btn
					v-for="month in months" :key="month.value" thin :flat="month.value !== currentMonthValue.month"
					:disabled="month.disabled" :value="month.name" @click="selectMonth(month.value)"
				/>
			</div>

			<div v-else class="year-grid grid gap-05 p-0">
				<Btn
					v-for="year in years" :key="year.value" thin :flat="year.value !== currentMonthValue.year"
					:disabled="year.disabled" :value="year.value.toString()" @click="selectYear(year.value)"
				/>
			</div>
		</div>

		<div
			v-if="enableTime && currentView === 'days'"
			class="time-picker border-start flex column gap-1 w-120px px-025"
		>
			<div class="flex gap-025">
				<NumberInput
					center :modelValue="hours" :disabled="!selectedDate" :min="0" :max="23" layout="vertical"
					:padZero="2" @update:modelValue="handleHourInput"
				/>
				<p class="pb-075">
					:
				</p>
				<NumberInput
					center :modelValue="minutes" :disabled="!selectedDate" :min="0" :max="59" :padZero="2"
					layout="vertical" @update:modelValue="handleMinuteInput"
				/>
			</div>
		</div>
	</div>
</template>

<style scoped>

.calendar-container {
max-width: 90vw;
border-radius: var(--bgl-card-border-radius);
background: var(--bgl-box-bg);
}

.calendar-section {
min-width: 280px;
}

.calendar-grid {
grid-template-columns: repeat(7, 1fr);
}

.month-grid {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(4, 1fr);
}

.year-grid {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(6, 1fr);
}

.month-item,
.year-item {
background: none;
color: var(--bgl-text-color);
}

.month-item:hover:not(.disabled),
.year-item:hover:not(.disabled) {
background: var(--bgl-box-bg);
filter: var(--bgl-hover-filter);
}

.month-item:active:not(.disabled),
.year-item:active:not(.disabled) {
background: var(--bgl-box-bg);
filter: var(--bgl-active-filter);
}

.month-item.selected,
.year-item.selected {
background-color: var(--bgl-primary);
color: white;
}

.month-item.disabled,
.year-item.disabled {
opacity: 0.6;
filter: grayscale(0.3);
}

.day {
border: none;
background: none;
color: var(--bgl-text-color);
}

.day:hover:not(.disabled) {
background-color: var(--bgl-input-bg);
color: var(--bgl-text-color);
outline: 1px solid var(--bgl-border-color);
}

.day:hover:not(.disabled).selected {
filter: var(--bgl-hover-filter);
background-color: var(--bgl-primary);
color: var(--bgl-white);
}

.day.selected {
background-color: var(--bgl-primary);
color: var(--bgl-white);
}

.day.today:not(.selected) {
border: 1px solid var(--bgl-primary);
}

.day.disabled {
opacity: 0.6;
filter: grayscale(0.3);
cursor: not-allowed;
}

.day.not-in-month {
opacity: 0.4;
}

.day.highlighted:not(.selected) {
background-color: var(--bgl-secondary-light, rgba(var(--bgl-primary-rgb, 0, 123, 255), 0.15));
font-weight: 500;
}

.day.highlighted.selected {
/* Add a subtle ring effect for dates that are both selected and highlighted */
box-shadow: 0 0 0 2px var(--bgl-secondary, rgba(var(--bgl-primary-rgb, 0, 123, 255), 0.5));
}

.day.highlighted:not(.selected):hover {
background-color: var(--bgl-secondary-light-hover, rgba(var(--bgl-primary-rgb, 0, 123, 255), 0.25));
}

</style>
