<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 } 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 }

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

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

// Process events for display with positioning calculations
const processedEvents = computed(() => {
	const events: WeekViewEvent[] = []
	const overlappingGroups: { [key: string]: WeekViewEvent[] } = {}

	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 - make sure to use the same formula everywhere
		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
		}

		// Group by day and time range for overlapping calculation
		const key = `${dayIndex}-${Math.floor(startMinutes / 30)}`
		if (!overlappingGroups[key]) {
			overlappingGroups[key] = []
		}
		overlappingGroups[key].push(weekEvent)
	})

	// Adjust overlapping events
	Object.values(overlappingGroups).forEach((group) => {
		if (group.length > 1) {
			const groupWidth = 100 / group.length
			group.forEach((event, index) => {
				event.overlappingEvents = group.length
				event.position = index
				event.width = groupWidth
				event.left = index * groupWidth
			})
		}
		events.push(...group)
	})

	return events
})

// Mouse event handlers for drag-to-create functionality
function handleMouseDown(e: MouseEvent, day: Date) {
	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
	}

	// Create a clean copy of the day
	const cleanDay = new Date(day)
	cleanDay.setHours(0, 0, 0, 0)

	const startTime = getTimeFromPosition(e.clientY, cleanDay)
	dragState.value.startTime = startTime

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

function handleMouseMove(e: MouseEvent) {
	if (!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
	const dayColumnsRect = dayColumnsContainer.value.getBoundingClientRect()
	const minDayIndex = Math.min(dragState.value.startDay, targetDayIndex)
	const maxDayIndex = Math.max(dragState.value.startDay, targetDayIndex)

	// Get the left edge of the leftmost selected day
	const leftDayEl = dayColumns.value[minDayIndex]
	if (!leftDayEl) return
	const leftDayRect = leftDayEl.getBoundingClientRect()
	const leftEdge = leftDayRect.left - dayColumnsRect.left

	// Get the right edge of the rightmost selected day
	const rightDayEl = dayColumns.value[maxDayIndex]
	if (!rightDayEl) return
	const rightDayRect = rightDayEl.getBoundingClientRect()
	const rightEdge = rightDayRect.right - dayColumnsRect.left

	dragState.value.endDay = targetDayIndex
	dragState.value.end = {
		x: rightEdge,
		y: e.clientY - dayColumnsRect.top
	}

	// Ensure the selection spans the full width of all selected days
	dragState.value.start.x = leftEdge

	if (targetDayIndex >= 0 && targetDayIndex < weekDays.value.length) {
		// Create a clean copy of the day
		const targetDay = new Date(weekDays.value[targetDayIndex])
		targetDay.setHours(0, 0, 0, 0)

		const endTime = getTimeFromPosition(e.clientY, targetDay)
		dragState.value.endTime = endTime
	}
}

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

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

	// Ensure end time is after start time - handle multi-day and reversed selections
	if (dragState.value.startDay > dragState.value.endDay
		|| (dragState.value.startDay === dragState.value.endDay && end < start)) {
		[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)
}

function getTimeFromPosition(y: number, day: Date): Date {
	if (!calendarGrid.value || !timeSlotsContainer.value) return new Date(day)

	const gridRect = calendarGrid.value.getBoundingClientRect()

	// Calculate relative Y position
	const relativeY = y - gridRect.top

	// Use the same formula as for 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) - 1)
	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
	}
}

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

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"
			>
				{{ formatDate(day, { fmt: '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, { fmt: 'DD' }) }}
				</span>
			</div>
		</div>
		<div 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, { fmt: 'HH:mm' }) }} - {{ formatDate(event.end_time, { fmt: 'HH:mm' }) }}
								</div>
							</div>
						</div>
					</template>
				</div>

				<!-- Drag selection preview - ensure it aligns with time slots and events -->
				<div
					v-if="dragState.isDragging && dragState.start && dragState.end"
					class="drag-preview absolute bg-primary-tint pointer-events-none overflow-hidden opacity-7 z-1"
					:style="{
						left: `${Math.min(dragState.start.x, dragState.end.x)}px`,
						top: `${Math.min(dragState.start.y, dragState.end.y)}px`,
						width: `${Math.abs(dragState.end.x - dragState.start.x)}px`,
						height: `${Math.abs(dragState.end.y - dragState.start.y)}px`,
					}"
				>
					<div class="color-primary txt-12 p-025">
						{{ dragState.startTime ? formatDate(dragState.startTime, { fmt: 'HH:mm' }) : '' }} - {{ dragState.endTime ? formatDate(dragState.endTime, { fmt: '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; */
}
</style>
