<script setup lang="ts">
import type { SetupContext, ComponentPublicInstance } from 'vue'
import type { CalendarEvent, WeekStart } from '../CalendarTypes'
import { formatDate } from '@bagelink/vue'
import { ref, computed, onMounted, onUnmounted, useSlots, nextTick } from 'vue'

interface WeekViewEvent extends CalendarEvent {
	top: number
	height: number
	left: number
	width: number
	overlappingEvents: number
	position: number
}

const props = withDefaults(defineProps<{
	events?: CalendarEvent[]
	startDate?: Date
	weekStart?: WeekStart
}>(), {
	startDate: () => new Date(),
	weekStart: 'Sunday',
	events: () => []
})

const emit = defineEmits<{
	(e: 'eventClick', event: CalendarEvent): void
	(e: 'eventCreate', event: { start_time: Date, end_time: Date }): void
	(e: 'openPopover', event: CalendarEvent, position?: { top: number, left: number }): void
}>()

const slots: SetupContext['slots'] = useSlots()

// Configuration constants
const slotHeight = 60
const slotDuration = 60
const timeRange = { start: 0, end: 24 }

// Check if drag-to-create is enabled based on whether eventCreate handler is provided
const isDragToCreateEnabled = computed(() => {
	return !!slots.eventCreate || emit.hasOwnProperty('eventCreate')
})

// Drag state
const dragState = ref({
	isDragging: false,
	startDay: -1, // Index of the starting day
	endDay: -1, // Index of the ending day
	start: undefined as { x: number, y: number } | undefined,
	end: undefined as { x: number, y: number } | undefined,
	startTime: undefined as Date | undefined,
	endTime: undefined as Date | undefined
})

// Time indicators
const currentTimeTop = ref(0)
const isToday = ref(false)
const currentTimeInterval = ref<any>()

const timeSlotsContainer = ref<HTMLElement>()
const calendarGrid = ref<HTMLElement>()
const dayColumns = ref<HTMLElement[]>([])
const dayColumnsContainer = ref<HTMLElement>()
const scrollableContainer = ref<HTMLElement>()

// Calculate week days based on start date and week start preference
const weekDays = computed(() => {
	const days = []
	const start = new Date(props.startDate)
	const weekStartOffset = props.weekStart === 'Sunday' ? 0 : 1

	// Adjust to start of week
	const dayOfWeek = start.getDay()
	start.setDate(start.getDate() - dayOfWeek + weekStartOffset)
	start.setHours(0, 0, 0, 0) // Set time to start of day

	// Create array of 7 day dates
	for (let i = 0; i < 7; i++) {
		const date = new Date(start)
		date.setDate(start.getDate() + i)
		days.push(date)
	}
	return days
})

// Generate hourly time slots
const timeSlots = computed(() => {
	const slots = []
	for (let hour = timeRange.start; hour < timeRange.end; hour++) {
		for (let minute = 0; minute < 60; minute += slotDuration) {
			slots.push({
				hour,
				minute,
				time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`
			})
		}
	}
	return slots
})

// Calculate drag preview positioning using the same formula as events
const dragPreviewPosition = computed(() => {
	if (!dragState.value.isDragging || !dragState.value.startTime || !dragState.value.endTime) {
		return null
	}

	// Use the exact same calculation as events
	const startMinutes = dragState.value.startTime.getHours() * 60 + dragState.value.startTime.getMinutes()
	const endMinutes = dragState.value.endTime.getHours() * 60 + dragState.value.endTime.getMinutes()

	const top = (startMinutes / slotDuration) * slotHeight
	const height = Math.max(((endMinutes - startMinutes) / slotDuration) * slotHeight, 30)

	return { top, height }
})

// Process events for display with positioning calculations
const processedEvents = computed(() => {
	const events: WeekViewEvent[] = []
	const dayEvents: { [dayIndex: number]: WeekViewEvent[] } = {}

	// First pass: group events by day and calculate basic positioning
	props.events.forEach((event) => {
		const startDate = new Date(event.start_time)
		const endDate = new Date(event.end_time)

		// Find which day this event belongs to
		const dayIndex = weekDays.value.findIndex(day => day.getDate() === startDate.getDate()
			&& day.getMonth() === startDate.getMonth()
			&& day.getFullYear() === startDate.getFullYear()
		)

		if (dayIndex === -1) { return }

		// Calculate vertical positioning
		const startMinutes = startDate.getHours() * 60 + startDate.getMinutes()
		const endMinutes = endDate.getHours() * 60 + endDate.getMinutes()
		const top = (startMinutes / slotDuration) * slotHeight
		const height = Math.max(((endMinutes - startMinutes) / slotDuration) * slotHeight, 30)

		const weekEvent: WeekViewEvent = {
			...event,
			top,
			height,
			left: 0,
			width: 100,
			overlappingEvents: 0,
			position: 0
		}

		if (!dayEvents[dayIndex]) {
			dayEvents[dayIndex] = []
		}
		dayEvents[dayIndex].push(weekEvent)
	})

	// Second pass: detect and handle overlapping events for each day
	Object.entries(dayEvents).forEach(([_, dayEventList]) => {
		// Sort events by start time
		dayEventList.sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime())

		// Group overlapping events
		const overlappingGroups: WeekViewEvent[][] = []
		let currentGroup: WeekViewEvent[] = []

		dayEventList.forEach((event) => {
			if (currentGroup.length === 0) {
				currentGroup = [event]
			} else {
				// Check if this event overlaps with any event in the current group
				const overlaps = currentGroup.some((groupEvent) => {
					const eventStart = new Date(event.start_time).getTime()
					const eventEnd = new Date(event.end_time).getTime()
					const groupEventStart = new Date(groupEvent.start_time).getTime()
					const groupEventEnd = new Date(groupEvent.end_time).getTime()

					return !(eventEnd <= groupEventStart || eventStart >= groupEventEnd)
				})

				if (overlaps) {
					currentGroup.push(event)
				} else {
					// No overlap, finalize current group and start new one
					if (currentGroup.length > 0) {
						overlappingGroups.push([...currentGroup])
					}
					currentGroup = [event]
				}
			}
		})

		// Don't forget the last group
		if (currentGroup.length > 0) {
			overlappingGroups.push(currentGroup)
		}

		// Position overlapping events with elegant spacing
		overlappingGroups.forEach((group) => {
			if (group.length > 1) {
				// Add some spacing between overlapping events (2% gap)
				const gap = 2
				const availableWidth = 100 - (gap * (group.length - 1))
				const eventWidth = availableWidth / group.length

				group.forEach((event, index) => {
					event.overlappingEvents = group.length
					event.position = index
					event.width = eventWidth
					event.left = index * (eventWidth + gap)
				})
			}
			events.push(...group)
		})
	})

	return events
})

// Mouse event handlers for drag-to-create functionality
function handleMouseDown(e: MouseEvent, day: Date) {
	// Only allow drag-to-create if the feature is enabled
	if (!isDragToCreateEnabled.value) { return }

	if (!calendarGrid.value || !dayColumnsContainer.value) { return }

	// Find the clicked day column
	let clickedDayIndex = -1
	for (let i = 0; i < dayColumns.value.length; i++) {
		const columnEl = dayColumns.value[i]
		if (!columnEl) { continue }

		const rect = columnEl.getBoundingClientRect()
		if (e.clientX >= rect.left && e.clientX <= rect.right) {
			clickedDayIndex = i
			break
		}
	}

	if (clickedDayIndex === -1) { return }

	// Get the clicked day column's position and dimensions
	const dayColumnRect = dayColumns.value[clickedDayIndex].getBoundingClientRect()
	const dayColumnsRect = dayColumnsContainer.value.getBoundingClientRect()

	dragState.value.isDragging = true
	dragState.value.startDay = clickedDayIndex
	dragState.value.endDay = clickedDayIndex
	dragState.value.start = {
		x: dayColumnRect.left - dayColumnsRect.left, // Left edge of the day column
		y: e.clientY - dayColumnsRect.top
	}
	dragState.value.end = {
		x: dayColumnRect.right - dayColumnsRect.left, // Right edge of the day column
		y: e.clientY - dayColumnsRect.top
	}

	// Use the same positioning calculation as events for consistency
	const startTime = getTimeFromPositionConsistent(e.clientY, day)
	dragState.value.startTime = startTime

	document.addEventListener('mousemove', handleMouseMove)
	document.addEventListener('mouseup', handleMouseUp)
}

function handleMouseMove(e: MouseEvent) {
	if (!isDragToCreateEnabled.value || !dragState.value.isDragging || !dragState.value.start || !dayColumns.value.length || !calendarGrid.value || !dayColumnsContainer.value) { return }

	// Find which day column we're hovering over
	let targetDayIndex = -1
	for (let i = 0; i < dayColumns.value.length; i++) {
		const columnEl = dayColumns.value[i]
		if (!columnEl) { continue }

		const rect = columnEl.getBoundingClientRect()
		if (e.clientX >= rect.left && e.clientX <= rect.right) {
			targetDayIndex = i
			break
		}
	}

	if (targetDayIndex === -1) { return }

	// Update which days are selected
	dragState.value.endDay = targetDayIndex
	dragState.value.end = {
		x: dragState.value.start!.x, // Keep x position bound to starting day column
		y: e.clientY - dayColumnsContainer.value.getBoundingClientRect().top
	}

	if (targetDayIndex >= 0 && targetDayIndex < weekDays.value.length) {
		// Always use the starting day as the base date for the event
		const startDay = new Date(weekDays.value[dragState.value.startDay])
		startDay.setHours(0, 0, 0, 0)

		// Calculate end time based on mouse position but on the starting day
		// Use the consistent positioning function for perfect alignment
		const endTime = getTimeFromPositionConsistent(e.clientY, startDay)
		dragState.value.endTime = endTime
	}
}

function handleMouseUp() {
	if (!isDragToCreateEnabled.value || !dragState.value.isDragging || !dragState.value.startTime || !dragState.value.endTime) { return }

	let start = new Date(dragState.value.startTime)
	let end = new Date(dragState.value.endTime)

	// Always use the starting day for the event, but allow time selection across days
	// The event will be created on the starting day with the selected time range
	if (end < start) {
		// If end time is before start time, swap them but keep the starting day
		[start, end] = [end, start]
	}

	emit('eventCreate', { start_time: start, end_time: end })

	// Reset drag state
	dragState.value = {
		isDragging: false,
		startDay: -1,
		endDay: -1,
		start: undefined,
		end: undefined,
		startTime: undefined,
		endTime: undefined
	}

	document.removeEventListener('mousemove', handleMouseMove)
	document.removeEventListener('mouseup', handleMouseUp)
}

// Consistent time positioning function that uses the same coordinate system as events
function getTimeFromPositionConsistent(y: number, day: Date): Date {
	if (!dayColumnsContainer.value) { return new Date(day) }

	const containerRect = dayColumnsContainer.value.getBoundingClientRect()

	// Calculate relative Y position from the container (same as events use)
	const relativeY = y - containerRect.top

	// Use the exact same formula as events
	const minutesPerSlot = slotDuration
	const slotsFromTop = relativeY / slotHeight
	const minutes = Math.floor(slotsFromTop * minutesPerSlot)

	const date = new Date(day)
	date.setHours(Math.floor(minutes / 60))
	const roundedMinutes = Math.round((minutes % 60) / 5) * 5
	date.setMinutes(roundedMinutes)
	date.setSeconds(0)
	date.setMilliseconds(0)

	return date
}

// Update the current time indicator to use the same calculation as events
function updateCurrentTimeIndicator() {
	const now = new Date()

	// Check if today is in our current week view
	isToday.value = weekDays.value.some(day => day.getDate() === now.getDate()
		&& day.getMonth() === now.getMonth()
		&& day.getFullYear() === now.getFullYear()
	)

	// Calculate position for the current time line - use same formula as events
	const minutes = now.getHours() * 60 + now.getMinutes()
	currentTimeTop.value = (minutes / slotDuration) * slotHeight
}

// Update the event handling to emit the openPopover event
function handleEventSelection(event: CalendarEvent, domEvent?: MouseEvent) {
	if (!slots.eventContent) {
		emit('eventClick', event)
		return
	}

	// Calculate position from DOM event if available
	if (domEvent) {
		const rect = (domEvent.currentTarget as HTMLElement).getBoundingClientRect()
		const position = {
			top: rect.top + window.scrollY + (rect.height / 2),
			left: rect.left + window.scrollX + rect.width + 10
		}
		emit('openPopover', event, position)
	} else {
		emit('openPopover', event)
	}
}

function setDayColumnRef(el: Element | ComponentPublicInstance | null | undefined, index: number) {
	// Ensure the array is the right size
	while (dayColumns.value.length <= index) {
		dayColumns.value.push(null as unknown as HTMLElement)
	}

	if (el) {
		dayColumns.value[index] = el instanceof Element ? el as HTMLElement : (el.$el as HTMLElement)
	} else {
		// If el is undefined or null, we might still want to keep track of this slot
		dayColumns.value[index] = null as unknown as HTMLElement
	}
}

function scrollToTime(time: number) {
	if (scrollableContainer.value) {
		scrollableContainer.value.scrollTop = time * slotHeight
	}
}

// Lifecycle hooks
onMounted(() => {
	updateCurrentTimeIndicator()
	currentTimeInterval.value = setInterval(updateCurrentTimeIndicator, 60000)

	// Scroll to 6:00 AM on mount
	nextTick(() => { scrollToTime(6) })
})

onUnmounted(() => {
	document.removeEventListener('mousemove', handleMouseMove)
	document.removeEventListener('mouseup', handleMouseUp)

	if (currentTimeInterval.value) {
		clearInterval(currentTimeInterval.value)
	}
})
</script>

<template>
	<div class="w-100p overflow-hidden m_overflow h-100p grid">
		<div ref="calendarGrid" class="weekGrid border-bottom me-1">
			<div />
			<div v-for="day in weekDays" :key="day.toISOString()" class="day-header p-05 txt-center m_txt12">
				{{ formatDate(day, { format: 'DDD' }) }}
				<span
					class="txt-12 round p-025" :class="{
						'color-gray': day.toDateString() !== new Date().toDateString(),
						'bg-primary color-white': day.toDateString() === new Date().toDateString(),
					}"
				>
					{{ formatDate(day, { format: 'DD' }) }}
				</span>
			</div>
		</div>
		<div ref="scrollableContainer" class="overflow h-100p pe-05">
			<div ref="dayColumnsContainer" class="weekGrid border-end relative">
				<div ref="timeSlotsContainer">
					<div
						v-for="slot in timeSlots" :key="slot.time"
						class="txt-light txt-12 color-gray flex justify-content-center bg-gray-light border-start"
						:style="{ height: `${slotHeight}px` }"
					>
						{{ slot.time }}
					</div>
				</div>

				<!-- Current time indicator - ensure it aligns with the time slots and events -->
				<div
					v-if="isToday" class="absolute end w-100p z-2 flex pointer-events-none ps-4-5"
					:style="{ top: `${currentTimeTop}px` }"
				>
					<div class="current-time-dot h-10px aspect-ratio-1 round bg-primary" />
					<div class="current-time-line w-100p bg-primary" />
				</div>

				<div
					v-for="(day, index) in weekDays" :key="day.toISOString()" :ref="el => setDayColumnRef(el, index)"
					class="day-column top bottom border-start relative" @mousedown="handleMouseDown($event, day)"
				>
					<template
						v-for="event in processedEvents.filter(e =>
							new Date(e.start_time).toDateString() === day.toDateString())" :key="event.id"
					>
						<div
							class="event absolute radius-05 overflow-hidden pointer transition z-1 opacity-8 border"
							:style="{
								top: `${event.top}px`,
								height: `${event.height}px`,
								left: `${event.left}%`,
								width: `${event.width}%`,
								backgroundColor: event.color || 'var(--bgl-primary)',
							}" @mousedown.stop @click.stop="handleEventSelection(event, $event)"
						>
							<div class="overflow-hidden color-white p-025 txt12 h-100p">
								<div class="white-space ellipsis-1">
									{{ event.title }}
								</div>
								<div class="txt10 opacity-8 user-select-none">
									{{ formatDate(event.start_time, { format: 'HH:mm' }) }} - {{ formatDate(event.end_time,
										{ format: 'HH:mm' }) }}
								</div>
							</div>
						</div>
					</template>
				</div>

				<!-- Drag selection preview - ensure it aligns with time slots and events -->
				<div
					v-if="isDragToCreateEnabled && dragState.isDragging && dragState.start && dragState.end && dragPreviewPosition"
					class="drag-preview absolute bg-primary-tint pointer-events-none overflow-hidden opacity-7 z-1"
					:style="{
						left: `${dragState.start.x}px`,
						top: `${dragPreviewPosition.top}px`,
						width: `${dayColumns[dragState.startDay]?.offsetWidth || 100}px`,
						height: `${dragPreviewPosition.height}px`,
					}"
				>
					<div class="color-primary txt-12 p-025">
						{{ dragState.startTime ? formatDate(dragState.startTime, { format: 'HH:mm' }) : '' }} - {{
							dragState.endTime
								? formatDate(dragState.endTime, { format: 'HH:mm' }) : '' }}
					</div>
				</div>
			</div>
		</div>
		<div class="border-bottom me-1" />
	</div>
</template>

<style scoped>
.weekGrid {
	display: grid;
	grid-template-columns: 5rem repeat(7, 1fr);
}

.days-column {
	flex-grow: 1;
	display: flex;
}

.day-column {
	flex: 1;
	position: relative;
	/* Ensure absolute positioning works properly */
	min-height: 100%;
	/* Ensure column fills the full height */
}

/* Add consistent styling for the time slots */
.txt-light {
	padding: 0.5rem 0;
	box-sizing: border-box;
	display: flex;
	align-items: flex-start;
	justify-content: center;
}

.event {
	margin-right: 2px;
	box-sizing: border-box;
}

.event:hover {
	z-index: 2;
}

.drag-preview {
	border: 1px solid var(--bgl-primary);
	background-color: rgba(var(--bgl-primary-rgb), 0.1);
	box-sizing: border-box;
	/* Ensure consistent sizing */
}

.current-time-line {
	height: 2px;
	background-color: var(--bgl-primary);
}

.current-time-dot {
	background-color: var(--bgl-primary);
	margin-right: -5px;
	/* Adjust dot position */
}

/* This ensures all grid content aligns properly */
.overflow {
	position: relative;
}

/* Time column headers should be aligned with their content */
.day-header {
	/* box-sizing: border-box;
	height: 100%;
	display: flex;
	flex-direction: column;
	justify-content: center; */
}

@media (max-width: 910px) {
	.weekGrid {
		display: grid;
		grid-template-columns: 3rem repeat(7, 100px);
	}
}
</style>
