<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)[]>
	}>(),
	{
		mode: () => ({ mode: 'day' }),
		firstDayOfWeek: WEEK_START_DAY.SUNDAY,
		locale: '',
		enableTime: false
	},
)

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

const computedHighlightedDates = $computed(() => toValue(props.highlightedDates))

// 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)

		if (minDate && date < minDate) return true
		if (maxDate && date > maxDate) 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,
		isYearDisabled
	}
}

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

	return {
		selectedDate
	}
}

// Initialize core composables
const { isDateDisabled, 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: isDateDisabled(date)
		}
	}))

	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) || !computedHighlightedDates.length) {
			return false
		}

		return computedHighlightedDates.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 flex gap-075 m_flex-wrap calendar-container justify-content-center h-100p">
		<div class="calendar-section m_border-none pe-05 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;
}

.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(--input-bg);
	color: var(--bgl-text-color);
	outline: 1px solid var(--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>
