<script setup lang="ts">
import { sleep, useDevice } from '@bagelink/vue'
import { ref, onMounted, onBeforeUnmount, watch, computed, nextTick, onUpdated } from 'vue'

type Easing = 'ease-in-out' | 'ease-in' | 'ease-out' | 'linear'
type AutoplayMode = 'disabled' | 'standard' | 'linear'
type AutoplayValue = boolean | AutoplayMode

interface CarouselOptions {
	speed?: number
	easing?: Easing
	perPage?: number | { [key: number]: number }
	slideWidth?: number
	slideGap?: number
	startIndex?: number
	draggable?: boolean
	multipleDrag?: boolean
	threshold?: number
	loop?: boolean
	rtl?: boolean
	autoplay?: AutoplayValue
	autoplayInterval?: number
	autoplaySpeed?: number
	pauseOnHover?: boolean
	dots?: boolean
	onInit?: () => void
	onChange?: () => void
}

interface DragState {
	startX: number
	endX: number
	startY: number
	letItGo: boolean | null
	preventClick: boolean
}

const props = withDefaults(defineProps<CarouselOptions>(), {
	speed: 350,
	easing: 'ease-out',
	startIndex: 0,
	draggable: true,
	multipleDrag: true,
	threshold: 20,
	loop: true,
	rtl: false,
	autoplay: 'disabled',
	autoplayInterval: 5000,
	pauseOnHover: true,
	dots: false,
	slideWidth: 300,
	slideGap: 1
})

const emit = defineEmits<{
	(event: 'init'): void
	(event: 'change'): void
}>()

// Basic state refs
const carouselRef = ref<HTMLElement | null>(null)
const sliderFrame = ref<HTMLDivElement | null>(null)
const innerElements = ref<Element[]>([])
const currentSlide = ref(0)
const selectorWidth = ref(0)
const { innerWidth } = useDevice()
const transformProperty = ref('transform')
const pointerDown = ref(false)

// Add a flag to track if we're in the middle of initializing or rebuilding
const isBuilding = ref(false)

// Drag-related refs
const drag = ref<DragState>({
	startX: 0,
	endX: 0,
	startY: 0,
	letItGo: null,
	preventClick: false
})

// Add a new variable to track actual dragging state
const isDragging = ref(false)
const dragThreshold = 10 // Increase threshold slightly for better detection

// Add autoplay-related refs
const autoplayTimer = ref<number | null>(null)
const linearAnimationFrame = ref<number | null>(null)
const isHovering = ref(false)
const linearOffset = ref(0)

// Add a counter to track rebuild attempts with no items
const emptyRebuildAttempts = ref(0)
const MAX_EMPTY_REBUILD_ATTEMPTS = 3

// Modified to work with optional parameters and ensure compatibility with Required type
type ComputedConfig = Omit<Required<CarouselOptions>, 'autoplay'> & { autoplay: AutoplayMode }

// perPage calculation - independent of config
const perPage = computed(() => {
	if (typeof props.perPage === 'number') {
		return props.perPage
	}

	if (typeof props.perPage === 'object') {
		return Object.entries(props.perPage)
			.reduce((acc, [viewport, value]) => innerWidth.value >= Number(viewport) ? value : acc, 1)
	}

	// If perPage is undefined, calculate based on slideWidth
	return Math.max(1, Math.ceil(selectorWidth.value / props.slideWidth))
})

// Core state
const config = computed<ComputedConfig>(() => {
	// Convert boolean autoplay values to string mode
	let autoplayMode: AutoplayMode = 'disabled'

	if (typeof props.autoplay === 'boolean') {
		autoplayMode = props.autoplay ? 'standard' : 'disabled'
	} else {
		autoplayMode = props.autoplay as AutoplayMode
	}

	// Calculate perPage fallback based on config
	const calculatedPerPage = perPage.value

	return {
		speed: props.speed,
		easing: props.easing,
		perPage: calculatedPerPage,
		startIndex: props.startIndex,
		draggable: props.draggable,
		multipleDrag: props.multipleDrag,
		threshold: props.threshold,
		loop: props.loop,
		rtl: props.rtl,
		autoplay: autoplayMode,
		autoplayInterval: props.autoplayInterval,
		autoplaySpeed: props.speed / 4,
		pauseOnHover: props.pauseOnHover,
		dots: props.dots,
		slideWidth: props.slideWidth,
		slideGap: props.slideGap,
		onInit: () => { emit('init') },
		onChange: () => { emit('change') }
	}
})

// Computed property for total number of dots to display
const totalDots = computed(() => {
	if (!innerElements.value.length) { return 0 }
	return config.value.loop ? innerElements.value.length : Math.ceil(innerElements.value.length - perPage.value + 1)
})

// Add MutationObserver ref
const slotObserver = ref<MutationObserver | null>(null)

// Add a flag to track component mounted state
const isMounted = ref(false)

// Add a reference to the slot container
const slotContainer = ref<HTMLDivElement | null>(null)

// Lifecycle hooks
onMounted(() => {
	// Set the mounted flag first
	isMounted.value = true

	// Wait a tick to ensure slot content is rendered
	nextTick(() => {
		init()
	})
})

onBeforeUnmount(() => {
	// Clean up
	isMounted.value = false
	stopAutoplay()
	detachEvents()

	// Remove the observer
	if (slotObserver.value) {
		slotObserver.value.disconnect()
		slotObserver.value = null
	}

	// Clear DOM content
	if (carouselRef.value) {
		carouselRef.value.innerHTML = ''
	}
})

// Add an onUpdated hook that checks for slot content changes
onUpdated(() => {
	// Only check if we're already initialized
	if (isMounted.value && !isBuilding.value && emptyRebuildAttempts.value < MAX_EMPTY_REBUILD_ATTEMPTS) {
		// Check if slot content changed
		if (slotContainer.value) {
			const slotElements = Array.from(slotContainer.value.children)

			// Only update if count changed or first initialization
			if (slotElements.length !== innerElements.value.length || innerElements.value.length === 0) {
				// Clone the elements from slot
				innerElements.value = slotElements.map(el => el.cloneNode(true) as Element)

				// Schedule rebuild
				nextTick(() => {
					updateSlider()
				})
			}
		}
	}
})

// Watch for changes to props
watch(() => props, () => {
	// Props changed, might need to rebuild
	resizeHandler()
}, { deep: true })

// Helper functions
function webkitOrNot(): string {
	const { style } = document.documentElement
	if (typeof style.transform === 'string') {
		return 'transform'
	}
	return 'WebkitTransform'
}

// Event handlers
function attachEvents(): void {
	window.addEventListener('resize', resizeHandler)

	if (config.value.draggable && carouselRef.value) {
		const eventOptions = { passive: true }
		const touchMoveOptions = { passive: false }
		const mouseMoveOptions = { passive: false } // Non-passive option for mouse move
		const mouseDownOptions = { passive: false } // Non-passive option for mouse down

		// Touch events
		carouselRef.value.addEventListener('touchstart', touchstartHandler, eventOptions)
		carouselRef.value.addEventListener('touchend', touchendHandler, eventOptions)
		carouselRef.value.addEventListener('touchmove', touchmoveHandler, touchMoveOptions)

		// Mouse events
		carouselRef.value.addEventListener('mousedown', mousedownHandler, mouseDownOptions)
		carouselRef.value.addEventListener('mouseup', mouseupHandler, eventOptions)
		carouselRef.value.addEventListener('mouseleave', mouseleaveHandler, eventOptions)
		carouselRef.value.addEventListener('mousemove', mousemoveHandler, mouseMoveOptions)

		// Prevent default dragging of images and links
		carouselRef.value.addEventListener('dragstart', dragstartHandler)

		// Click
		carouselRef.value.addEventListener('click', clickHandler)
	}

	// Add mouse enter/leave events for pauseOnHover functionality
	if (config.value.autoplay !== 'disabled' && config.value.pauseOnHover && carouselRef.value) {
		carouselRef.value.addEventListener('mouseenter', mouseenterHandler)
		carouselRef.value.addEventListener('mouseleave', mouseleaveAutoplayHandler)
	}
}

function detachEvents(): void {
	window.removeEventListener('resize', resizeHandler)

	if (carouselRef.value) {
		carouselRef.value.removeEventListener('touchstart', touchstartHandler)
		carouselRef.value.removeEventListener('touchend', touchendHandler)
		carouselRef.value.removeEventListener('touchmove', touchmoveHandler)
		carouselRef.value.removeEventListener('mousedown', mousedownHandler)
		carouselRef.value.removeEventListener('mouseup', mouseupHandler)
		carouselRef.value.removeEventListener('mouseleave', mouseleaveHandler)
		carouselRef.value.removeEventListener('mousemove', mousemoveHandler)
		carouselRef.value.removeEventListener('dragstart', dragstartHandler)
		carouselRef.value.removeEventListener('click', clickHandler)
		carouselRef.value.removeEventListener('mouseenter', mouseenterHandler)
		carouselRef.value.removeEventListener('mouseleave', mouseleaveAutoplayHandler)
	}
}

// Core functionality
async function init(): Promise<void> {
	if (!carouselRef.value || !slotContainer.value || !isMounted.value) { return }

	transformProperty.value = webkitOrNot()

	// Add a small delay to ensure DOM is ready
	await sleep(10)

	// Create references by cloning slot content
	innerElements.value = Array.from(slotContainer.value.children).map(
		el => el.cloneNode(true) as Element
	)

	if (!innerElements.value.length) {
		console.warn('No carousel items found during initialization')
		return
	}

	selectorWidth.value = carouselRef.value.offsetWidth

	// Set current slide
	currentSlide.value = config.value.loop
		? config.value.startIndex % innerElements.value.length
		: Math.max(0, Math.min(config.value.startIndex, innerElements.value.length - perPage.value))

	// Hide overflow
	carouselRef.value.style.overflow = 'hidden'

	// Set RTL direction if needed
	carouselRef.value.style.direction = config.value.rtl ? 'rtl' : 'ltr'

	// Attach events
	attachEvents()

	// Set up the MutationObserver to watch for slot changes
	setupSlotObserver()

	// Build slider frame
	buildSliderFrame()

	// Start autoplay if enabled
	if (config.value.autoplay !== 'disabled') {
		startAutoplay()
	}

	// Call onInit callback
	config.value.onInit()
}

function buildSliderFrame(): void {
	if (!carouselRef.value || !isMounted.value) { return }

	// Set building flag to prevent interactions during rebuild
	isBuilding.value = true

	try {
		// Ensure we have elements to build the slider
		if (!innerElements.value.length) {
			console.warn('No carousel items found')
			emptyRebuildAttempts.value++
			isBuilding.value = false
			return
		}

		// Reset counter since we have elements
		emptyRebuildAttempts.value = 0

		const originalCount = innerElements.value.length
		const widthItem = selectorWidth.value / perPage.value

		// Calculate how many times to repeat items to fill big screens
		const minItemsNeeded = Math.max(perPage.value * 3, 20) // Ensure we have enough content for big screens
		const repetitions = Math.max(1, Math.ceil(minItemsNeeded / originalCount))
		const totalMainItems = originalCount * repetitions

		// Ultra-aggressive buffer size for really big screens
		const bufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
		const itemsToBuild = config.value.loop
			? totalMainItems + (2 * bufferSize)
			: totalMainItems

		// Create a clean slate for the slider
		carouselRef.value.innerHTML = ''

		// Create or recreate the slider frame
		if (!sliderFrame.value) {
			sliderFrame.value = document.createElement('div')
		} else {
			// Clear existing content
			while (sliderFrame.value.firstChild) {
				sliderFrame.value.removeChild(sliderFrame.value.firstChild)
			}
		}

		sliderFrame.value.style.width = `${widthItem * itemsToBuild}px`
		enableTransition()

		if (config.value.draggable) {
			carouselRef.value.style.cursor = '-webkit-grab'
		}

		// Create a fragment to hold all slider content
		const docFragment = document.createDocumentFragment()

		// Add clones before original items (for loop mode)
		if (config.value.loop && innerElements.value.length > 0) {
			for (let i = originalCount - bufferSize; i < originalCount; i++) {
				if (i >= 0 && i < originalCount) {
					const original = innerElements.value[i]
					const clone = original.cloneNode(true) as Element

					if (clone instanceof HTMLElement) {
						clone.setAttribute('data-clone', 'before')
						copyAttributesAndEvents(original, clone)
					}

					const wrapped = buildSliderFrameItem(clone)
					docFragment.appendChild(wrapped)
				}
			}
		}

		// Add main items repeated multiple times to fill big screens
		for (let rep = 0; rep < repetitions; rep++) {
			for (let i = 0; i < originalCount; i++) {
				const element = innerElements.value[i]
				const clone = element.cloneNode(true) as Element

				if (clone instanceof HTMLElement) {
					clone.setAttribute('data-repetition', rep.toString())
				}

				const wrapped = buildSliderFrameItem(clone)
				docFragment.appendChild(wrapped)
			}
		}

		// Add clones after original items (for loop mode)
		if (config.value.loop && innerElements.value.length > 0) {
			for (let i = 0; i < bufferSize; i++) {
				if (i >= 0 && i < originalCount) {
					const original = innerElements.value[i]
					const clone = original.cloneNode(true) as Element

					if (clone instanceof HTMLElement) {
						clone.setAttribute('data-clone', 'after')
						copyAttributesAndEvents(original, clone)
					}

					const wrapped = buildSliderFrameItem(clone)
					docFragment.appendChild(wrapped)
				}
			}
		}

		// Append all content to the slider frame
		sliderFrame.value.appendChild(docFragment)

		// Add the slider frame to the carousel
		carouselRef.value.appendChild(sliderFrame.value)

		// Update currentSlide if needed to stay within bounds
		if (currentSlide.value >= innerElements.value.length) {
			currentSlide.value = Math.max(0, innerElements.value.length - 1)
		}

		// Set initial position
		slideToCurrent()
	} catch (error) {
		console.error('Error building slider frame:', error)
	} finally {
		// Always make sure to reset the building flag
		isBuilding.value = false
	}
}

// Helper function to copy attributes and events from original to clone
function copyAttributesAndEvents(original: Element, clone: Element): void {
	if (!(original instanceof HTMLElement) || !(clone instanceof HTMLElement)) {
		return
	}

	// Copy all attributes
	const allAttrs = original.attributes
	for (let i = 0; i < allAttrs.length; i++) {
		const attr = allAttrs[i]
		if (attr.name !== 'id') { // Skip id to avoid duplicate IDs
			clone.setAttribute(attr.name, attr.value)
		}
	}

	// For Vue-specific elements, add a custom click handler that relays to the original
	clone.addEventListener('click', (e) => {
		// Check if original has a click handler
		if (typeof (original as any).click === 'function') {
			// We need to simulate the click on the original element
			// Create and dispatch a new click event on the original element
			const clickEvent = new MouseEvent('click', {
				bubbles: true,
				cancelable: true,
				view: window
			})
			original.dispatchEvent(clickEvent)

			// Stop propagation of the clone's event
			e.stopPropagation()
		}
	})

	// Special handling for links
	if (original.tagName === 'A' && clone.tagName === 'A') {
		const originalHref = original.getAttribute('href')
		if (originalHref) {
			clone.addEventListener('click', (e) => {
				e.preventDefault()

				// Get original onClick behavior
				if (original.onclick) {
					original.onclick(new MouseEvent('click'))
				} else {
					// Default behavior - navigate like original link would
					const targetBlank = original.getAttribute('target') === '_blank'
					if (targetBlank) {
						window.open(originalHref, '_blank')
					} else {
						window.location.href = originalHref
					}
				}
			})
		}
	}
}

function buildSliderFrameItem(elm: Element): HTMLElement {
	const elementContainer = document.createElement('div')
	elementContainer.style.cssFloat = config.value.rtl ? 'right' : 'left'
	elementContainer.style.float = config.value.rtl ? 'right' : 'left'
	elementContainer.style.padding = config.value.slideGap ? `${config.value.slideGap / 2}rem` : '0'

	// Calculate total items including repetitions for big screens
	const originalCount = innerElements.value.length
	const minItemsNeeded = Math.max(perPage.value * 3, 20)
	const repetitions = Math.max(1, Math.ceil(minItemsNeeded / originalCount))
	const totalMainItems = originalCount * repetitions

	// Ultra-aggressive buffer size - must match buildSliderFrame buffer size
	const itemBufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
	const percentage = config.value.loop
		? 100 / (totalMainItems + (2 * itemBufferSize))
		: 100 / totalMainItems

	elementContainer.style.width = `${percentage}%`
	elementContainer.appendChild(elm)
	return elementContainer
}

function slideToCurrent(enableTransitionFlag?: boolean): void {
	try {
		// Ensure component is still mounted
		if (!isMounted.value || !innerElements.value.length || !sliderFrame.value) { return }

		// Ultra-aggressive buffer size - must match buildSliderFrame buffer size
		const slideBufferSize = Math.max(perPage.value * 8, 20) // Even more buffer for huge screens
		const currentSlideValue = config.value.loop
			? currentSlide.value + slideBufferSize
			: currentSlide.value

		const offset = (config.value.rtl ? 1 : -1) * currentSlideValue * (selectorWidth.value / perPage.value)

		if (enableTransitionFlag && sliderFrame.value) {
			// Use requestAnimationFrame for smooth transitions
			requestAnimationFrame(() => {
				// Check again that we're still mounted and have the frame
				if (!isMounted.value || !sliderFrame.value) { return }

				requestAnimationFrame(() => {
					if (!isMounted.value || !sliderFrame.value) { return }

					enableTransition()
					sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
				})
			})
		} else if (sliderFrame.value) {
			sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset}px, 0, 0)`
		}
	} catch (error) {
		console.error('Error in slideToCurrent:', error)
	}
}

function prev(howManySlides: number = 1): void {
	if (!isMounted.value || isBuilding.value || !innerElements.value.length) { return }

	const beforeChange = currentSlide.value

	if (config.value.loop) {
		const isNewIndexClone = currentSlide.value - howManySlides < 0

		if (isNewIndexClone) {
			disableTransition()

			const mirrorSlideIndex = currentSlide.value + innerElements.value.length
			const mirrorSlideIndexOffset = perPage.value
			const moveTo = mirrorSlideIndex + mirrorSlideIndexOffset
			const offset = (config.value.rtl ? 1 : -1) * moveTo * (selectorWidth.value / perPage.value)
			const dragDistance = config.value.draggable ? drag.value.endX - drag.value.startX : 0

			if (sliderFrame.value) {
				sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset + dragDistance}px, 0, 0)`
			}

			currentSlide.value = mirrorSlideIndex - howManySlides
		} else {
			currentSlide.value -= howManySlides
		}
	} else {
		currentSlide.value = Math.max(currentSlide.value - howManySlides, 0)
	}

	if (beforeChange !== currentSlide.value) {
		slideToCurrent(config.value.loop)
		config.value.onChange()
	}
	if (config.value.autoplay !== 'disabled') {
		startAutoplay()
	}
}

function next(howManySlides: number = 1): void {
	if (!isMounted.value || isBuilding.value || !innerElements.value.length) { return }

	const beforeChange = currentSlide.value

	if (config.value.loop) {
		const isNewIndexClone = currentSlide.value + howManySlides > innerElements.value.length - perPage.value

		if (isNewIndexClone) {
			disableTransition()

			const mirrorSlideIndex = currentSlide.value - innerElements.value.length
			const mirrorSlideIndexOffset = perPage.value
			const moveTo = mirrorSlideIndex + mirrorSlideIndexOffset
			const offset = (config.value.rtl ? 1 : -1) * moveTo * (selectorWidth.value / perPage.value)
			const dragDistance = config.value.draggable ? drag.value.endX - drag.value.startX : 0

			if (sliderFrame.value) {
				sliderFrame.value.style[transformProperty.value as any] = `translate3d(${offset + dragDistance}px, 0, 0)`
			}

			currentSlide.value = mirrorSlideIndex + howManySlides
		} else {
			currentSlide.value += howManySlides
		}
	} else {
		currentSlide.value = Math.min(currentSlide.value + howManySlides, innerElements.value.length - perPage.value)
	}

	if (beforeChange !== currentSlide.value) {
		slideToCurrent(config.value.loop)
		config.value.onChange()
	}
	if (config.value.autoplay !== 'disabled') {
		startAutoplay()
	}
}

function goTo(index: number): void {
	if (innerElements.value.length <= perPage.value) {
		return
	}

	const beforeChange = currentSlide.value

	currentSlide.value = config.value.loop
		? index % innerElements.value.length
		: Math.min(Math.max(index, 0), innerElements.value.length - perPage.value)

	if (beforeChange !== currentSlide.value) {
		slideToCurrent()
		config.value.onChange()
	}
}

function disableTransition(): void {
	if (sliderFrame.value) {
		sliderFrame.value.style.webkitTransition = `all 0ms ${config.value.easing}`
		sliderFrame.value.style.transition = `all 0ms ${config.value.easing}`
	}
}

function enableTransition(): void {
	if (sliderFrame.value) {
		sliderFrame.value.style.webkitTransition = `all ${config.value.speed}ms ${config.value.easing}`
		sliderFrame.value.style.transition = `all ${config.value.speed}ms ${config.value.easing}`
	}
}

function updateAfterDrag(): void {
	if (!isMounted.value) { return }

	const movement = (config.value.rtl ? -1 : 1) * (drag.value.endX - drag.value.startX)
	const movementDistance = Math.abs(movement)
	const howManySliderToSlide = config.value.multipleDrag
		? Math.ceil(movementDistance / (selectorWidth.value / perPage.value))
		: 1

	const slideToNegativeClone = movement > 0 && currentSlide.value - howManySliderToSlide < 0
	const slideToPositiveClone = movement < 0
		&& currentSlide.value + howManySliderToSlide > innerElements.value.length - perPage.value

	if (movement > 0 && movementDistance > config.value.threshold && innerElements.value.length > perPage.value) {
		prev(howManySliderToSlide)
	} else if (movement < 0
		&& movementDistance > config.value.threshold
		&& innerElements.value.length > perPage.value) {
		next(howManySliderToSlide)
	}

	slideToCurrent(slideToNegativeClone || slideToPositiveClone)
}

function resizeHandler(): void {
	// Recalculate current slide
	if (currentSlide.value + perPage.value > innerElements.value.length) {
		currentSlide.value = innerElements.value.length <= perPage.value
			? 0
			: innerElements.value.length - perPage.value
	}

	if (carouselRef.value) {
		selectorWidth.value = carouselRef.value.offsetWidth
	}

	buildSliderFrame()
}

function clearDrag(): void {
	drag.value = {
		startX: 0,
		endX: 0,
		startY: 0,
		letItGo: null,
		preventClick: drag.value.preventClick
	}
}

// Touch event handlers
function touchstartHandler(e: TouchEvent): void {
	if (isBuilding.value || !isMounted.value) { return }

	const target = e.target as HTMLElement
	const ignoreTags = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].includes(target.nodeName)

	if (ignoreTags) {
		return
	}

	// Pause autoplay during touch interaction
	stopAutoplay()

	e.stopPropagation()
	pointerDown.value = true
	isDragging.value = false // Reset dragging state
	drag.value.startX = e.touches[0].pageX
	drag.value.startY = e.touches[0].pageY

	// Mark link-related elements for potential preventClick
	const isLink = target.tagName === 'A' || target.closest('a')
	const isImage = target.tagName === 'IMG'
	if (isLink || isImage) {
		drag.value.preventClick = false
	}

	// When starting drag during linear autoplay, save the current position
	if (config.value.autoplay === 'linear' && sliderFrame.value) {
		// Get the current transform value and save it to apply during dragging
		const currentTransform = getComputedStyle(sliderFrame.value)[transformProperty.value as any]
		if (currentTransform && currentTransform !== 'none') {
			try {
				// Parse the transform matrix to get the current X position
				const matrix = new DOMMatrix(currentTransform)
				const currentX = matrix.m41

				// Convert position to slides and update currentSlide
				const itemWidth = selectorWidth.value / perPage.value
				if (config.value.loop) {
					// Adjust for the perPage offset in loop mode
					const totalPosition = Math.abs(currentX)
					const slidePosition = Math.round(totalPosition / itemWidth) - perPage.value
					currentSlide.value = slidePosition >= 0
						? slidePosition % innerElements.value.length
						: (innerElements.value.length + (slidePosition % innerElements.value.length)) % innerElements.value.length
				} else {
					// In non-loop mode, just calculate the current slide
					const slidePosition = Math.round(Math.abs(currentX) / itemWidth)
					currentSlide.value = Math.min(Math.max(slidePosition, 0), innerElements.value.length - perPage.value)
				}
			} catch (e) {
				console.error('Error parsing transform matrix:', e)
			}
		}

		// Stop the animation immediately
		stopAutoplay()
	}
}

function touchendHandler(e: TouchEvent): void {
	if (isBuilding.value || !isMounted.value) { return }

	e.stopPropagation()

	// If we were dragging, prevent clicks by adding a short-lived capture event listener
	if (isDragging.value) {
		// Add a temporary, one-time capture event listener to block the next click
		document.addEventListener('click', function preventClickAfterDrag(event) {
			event.preventDefault()
			event.stopPropagation()
			event.stopImmediatePropagation() // This is crucial to stop other handlers from executing

			// Remove this listener after it's been triggered once
			document.removeEventListener('click', preventClickAfterDrag, true)
		}, { capture: true, once: true })
	}

	pointerDown.value = false
	isDragging.value = false
	enableTransition()

	if (drag.value.endX) {
		updateAfterDrag()
	}

	clearDrag()

	// Restart autoplay after touch interaction
	if (config.value.autoplay !== 'disabled' && !isHovering.value && isMounted.value) {
		startAutoplay()
	}
}

function touchmoveHandler(e: TouchEvent): void {
	if (isBuilding.value || !isMounted.value) { return }

	e.stopPropagation()

	if (drag.value.letItGo === null) {
		drag.value.letItGo = Math.abs(drag.value.startY - e.touches[0].pageY)
			< Math.abs(drag.value.startX - e.touches[0].pageX)
	}

	if (pointerDown.value && drag.value.letItGo) {
		e.preventDefault()
		drag.value.endX = e.touches[0].pageX

		// Calculate movement distance
		const dragDistance = Math.abs(drag.value.endX - drag.value.startX)

		// If dragged more than the threshold, mark as a real drag operation
		if (dragDistance > dragThreshold) {
			isDragging.value = true
			drag.value.preventClick = true
		}

		if (sliderFrame.value) {
			disableTransition()

			const currentSlideValue = config.value.loop
				? currentSlide.value + perPage.value
				: currentSlide.value

			const currentOffset = currentSlideValue * (selectorWidth.value / perPage.value)
			const dragOffset = drag.value.endX - drag.value.startX
			const offset = config.value.rtl
				? currentOffset + dragOffset
				: currentOffset - dragOffset

			sliderFrame.value.style[transformProperty.value as any]
				= `translate3d(${(config.value.rtl ? 1 : -1) * offset}px, 0, 0)`
		}
	}
}

// Mouse event handlers
function mousedownHandler(e: MouseEvent): void {
	if (isBuilding.value || !isMounted.value) { return }

	const target = e.target as HTMLElement
	const ignoreTags = ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].includes(target.nodeName)

	if (ignoreTags) { return }

	// Pause autoplay during mouse interaction
	stopAutoplay()

	e.preventDefault()
	e.stopPropagation()
	pointerDown.value = true
	isDragging.value = false // Reset dragging state on mousedown
	drag.value.startX = e.pageX

	// Mark link-related elements for potential preventClick
	const isLink = target.tagName === 'A' || target.closest('a')
	const isImage = target.tagName === 'IMG'
	if (isLink || isImage) {
		drag.value.preventClick = false // Start with false, will be set to true if actual dragging occurs
	}

	// When starting drag during linear autoplay, save the current position
	if (config.value.autoplay === 'linear' && sliderFrame.value) {
		// Get the current transform value and save it to apply during dragging
		const currentTransform = getComputedStyle(sliderFrame.value)[transformProperty.value as any]
		if (currentTransform && currentTransform !== 'none') {
			try {
				// Parse the transform matrix to get the current X position
				const matrix = new DOMMatrix(currentTransform)
				const currentX = matrix.m41

				// Convert position to slides and update currentSlide
				const itemWidth = selectorWidth.value / perPage.value
				if (config.value.loop) {
					// Adjust for the perPage offset in loop mode
					const totalPosition = Math.abs(currentX)
					const slidePosition = Math.round(totalPosition / itemWidth) - perPage.value
					currentSlide.value = slidePosition >= 0
						? slidePosition % innerElements.value.length
						: (innerElements.value.length + (slidePosition % innerElements.value.length)) % innerElements.value.length
				} else {
					// In non-loop mode, just calculate the current slide
					const slidePosition = Math.round(Math.abs(currentX) / itemWidth)
					currentSlide.value = Math.min(Math.max(slidePosition, 0), innerElements.value.length - perPage.value)
				}
			} catch (e) {
				console.error('Error parsing transform matrix:', e)
			}
		}

		// Stop the animation immediately
		stopAutoplay()
	}
}

function mouseupHandler(e: MouseEvent): void {
	if (isBuilding.value || !isMounted.value) { return }

	e.stopPropagation()

	// If we were dragging, prevent clicks by adding a short-lived capture event listener
	if (isDragging.value) {
		// Add a temporary, one-time capture event listener to block the next click
		document.addEventListener('click', function preventClickAfterDrag(event) {
			event.preventDefault()
			event.stopPropagation()
			event.stopImmediatePropagation() // This is crucial to stop other handlers from executing

			// Remove this listener after it's been triggered once
			document.removeEventListener('click', preventClickAfterDrag, true)
		}, { capture: true, once: true })
	}

	pointerDown.value = false
	isDragging.value = false

	if (carouselRef.value) {
		carouselRef.value.style.cursor = '-webkit-grab'
	}

	enableTransition()

	if (drag.value.endX) {
		updateAfterDrag()
	}

	clearDrag()

	// Restart autoplay after mouse interaction if not hovering
	if (config.value.autoplay !== 'disabled' && !isHovering.value && isMounted.value) {
		startAutoplay()
	}
}

function mousemoveHandler(e: MouseEvent): void {
	if (isBuilding.value || !isMounted.value) { return }

	e.preventDefault()

	if (pointerDown.value) {
		drag.value.endX = e.pageX

		// Calculate movement distance
		const dragDistance = Math.abs(drag.value.endX - drag.value.startX)

		// If dragged more than the threshold, mark as a real drag operation
		if (dragDistance > dragThreshold) {
			isDragging.value = true
			drag.value.preventClick = true
		}

		if (carouselRef.value) {
			carouselRef.value.style.cursor = '-webkit-grabbing'
		}

		if (sliderFrame.value) {
			disableTransition()

			const currentSlideValue = config.value.loop
				? currentSlide.value + perPage.value
				: currentSlide.value

			const currentOffset = currentSlideValue * (selectorWidth.value / perPage.value)
			const dragOffset = drag.value.endX - drag.value.startX
			const offset = config.value.rtl
				? currentOffset + dragOffset
				: currentOffset - dragOffset

			sliderFrame.value.style[transformProperty.value as any]
				= `translate3d(${(config.value.rtl ? 1 : -1) * offset}px, 0, 0)`
		}
	}
}

function mouseleaveHandler(e: MouseEvent): void {
	if (isBuilding.value || !isMounted.value) { return }

	if (pointerDown.value) {
		pointerDown.value = false

		if (carouselRef.value) {
			carouselRef.value.style.cursor = '-webkit-grab'
		}

		drag.value.endX = e.pageX
		drag.value.preventClick = false
		enableTransition()
		updateAfterDrag()
		clearDrag()
	}
}

function clickHandler(e: MouseEvent): void {
	if (isBuilding.value || !isMounted.value) { return }

	if (drag.value.preventClick || isDragging.value) {
		e.preventDefault()
		e.stopPropagation()
		e.stopImmediatePropagation() // This stops other handlers from firing
		drag.value.preventClick = false
	}

	drag.value.preventClick = false
}

// Add this new function to handle dragstart events
function dragstartHandler(e: DragEvent): void {
	if (isBuilding.value || !isMounted.value) { return } // Prevent interaction during build

	// Prevent default drag behavior for images and links
	if (config.value.draggable) {
		const target = e.target as HTMLElement
		const isDraggableElement = target.tagName === 'IMG'
			|| target.tagName === 'A'
			|| target.parentElement?.tagName === 'A'

		if (isDraggableElement) {
			e.preventDefault()
		}
	}
}

// Public API
function remove(index: number): void {
	if (index < 0 || index >= innerElements.value.length) {
		throw new Error('Item to remove doesn\'t exist 😭')
	}

	// Shift sliderFrame back by one if needed
	const lowerIndex = index < currentSlide.value
	const lastItem = currentSlide.value + perPage.value - 1 === index

	if (lowerIndex || lastItem) {
		currentSlide.value--
	}

	innerElements.value.splice(index, 1)
	buildSliderFrame()
}

function insert(item: Element, index: number): void {
	if (index < 0 || index > innerElements.value.length + 1) {
		throw new Error('Unable to insert at this index 😭')
	}

	if (innerElements.value.includes(item)) {
		throw new Error('The same item in a carousel? Really? Nope 😭')
	}

	// Avoid shifting content
	const shouldItShift = index <= currentSlide.value && innerElements.value.length > 0

	if (shouldItShift) {
		currentSlide.value++
	}

	innerElements.value.splice(index, 0, item)
	buildSliderFrame()
}

function prepend(item: Element): void {
	insert(item, 0)
}

function append(item: Element): void {
	insert(item, innerElements.value.length)
}

function destroy(restoreMarkup: boolean = false): void {
	detachEvents()

	if (carouselRef.value) {
		carouselRef.value.style.cursor = 'auto'

		if (restoreMarkup) {
			const slides = document.createDocumentFragment()

			for (let i = 0; i < innerElements.value.length; i++) {
				slides.appendChild(innerElements.value[i])
			}

			carouselRef.value.innerHTML = ''
			carouselRef.value.appendChild(slides)
			carouselRef.value.removeAttribute('style')
		}
	}
}

// Add after init()
async function startAutoplay(): Promise<void> {
	if (!isMounted.value) { return } // Don't start if not mounted

	stopAutoplay() // Clear any existing timers first

	if (config.value.autoplay === 'standard') {
		autoplayTimer.value = window.setInterval(() => {
			// Check if component is still mounted before advancing
			if (isMounted.value && !isHovering.value && !isBuilding.value) {
				next()
			}
		}, config.value.autoplayInterval)
	} else if (config.value.autoplay === 'linear') {
		startLinearAutoplay()
	}
}

function stopAutoplay(): void {
	if (autoplayTimer.value !== null) {
		clearInterval(autoplayTimer.value)
		autoplayTimer.value = null
	}

	if (linearAnimationFrame.value !== null) {
		cancelAnimationFrame(linearAnimationFrame.value)
		linearAnimationFrame.value = null
	}
}

function startLinearAutoplay(): void {
	if (!isMounted.value || !innerElements.value.length) { return }

	// Reset the linear offset to the current slide position
	const itemWidth = selectorWidth.value / perPage.value
	linearOffset.value = currentSlide.value * itemWidth
	let lastTimestamp = 0

	// Animation function
	const animate = (timestamp: number) => {
		// Always verify that the component is still mounted
		if (!isMounted.value || !sliderFrame.value) {
			stopAutoplay()
			return
		}

		if (!lastTimestamp) { lastTimestamp = timestamp }

		// Calculate time delta and convert to pixels based on speed
		const elapsed = timestamp - lastTimestamp
		lastTimestamp = timestamp

		// If hovering or not visible, just continue the animation loop without changing position
		if (isHovering.value) {
			linearAnimationFrame.value = requestAnimationFrame(animate)
			return
		}

		// Calculate how many pixels to move based on time elapsed and speed
		const pixelsToMove = (elapsed / 1000) * config.value.autoplaySpeed
		linearOffset.value += pixelsToMove

		// Calculate total width of all items
		const totalWidth = itemWidth * innerElements.value.length

		// If we've scrolled past the entire content, reset
		if (linearOffset.value >= totalWidth) {
			if (config.value.loop) {
				linearOffset.value = 0
			} else {
				// If not looping, stop at the end
				linearOffset.value = totalWidth - itemWidth
				stopAutoplay()
			}
		}

		// Apply the offset using transforms
		if (sliderFrame.value && !isBuilding.value) {
			const direction = config.value.rtl ? 1 : -1
			sliderFrame.value.style.transition = 'none' // Smooth scrolling, no easing

			// In loop mode, we need to offset by perPage
			let displayOffset = linearOffset.value
			if (config.value.loop) {
				displayOffset = linearOffset.value + (perPage.value * itemWidth)
			}

			sliderFrame.value.style[transformProperty.value as any]
				= `translate3d(${direction * displayOffset}px, 0, 0)`
		}

		// Update current slide based on the offset
		const newSlide = Math.floor(linearOffset.value / itemWidth) % innerElements.value.length
		if (newSlide !== currentSlide.value) {
			currentSlide.value = newSlide
			config.value.onChange()
		}

		// Continue animation loop only if mounted
		if (isMounted.value) {
			linearAnimationFrame.value = requestAnimationFrame(animate)
		}
	}

	// Start the animation only if mounted
	if (isMounted.value) {
		linearAnimationFrame.value = requestAnimationFrame(animate)
	}
}

// Event handlers for autoplay pausing
function mouseenterHandler(): void {
	isHovering.value = true
}

function mouseleaveAutoplayHandler(): void {
	isHovering.value = false
}

// Add new public methods for autoplay control
function pauseAutoplay(): void {
	stopAutoplay()
}

function resumeAutoplay(): void {
	if (config.value.autoplay !== 'disabled') {
		startAutoplay()
	}
}

// Add function to set up the MutationObserver
function setupSlotObserver(): void {
	if (!slotContainer.value || slotObserver.value) { return }

	// Create a new MutationObserver
	slotObserver.value = new MutationObserver((mutations) => {
		// Skip if building or over rebuild limit
		if (isBuilding.value || !isMounted.value
			|| emptyRebuildAttempts.value >= MAX_EMPTY_REBUILD_ATTEMPTS) {
			return
		}

		let needsRebuild = false

		for (const mutation of mutations) {
			if (mutation.type === 'childList') {
				needsRebuild = true
				break
			}
		}

		if (needsRebuild) {
			nextTick(() => {
				if (isMounted.value && slotContainer.value) {
					// Update by cloning the new content
					innerElements.value = Array.from(slotContainer.value.children).map(
						el => el.cloneNode(true) as Element
					)

					updateSlider()
				}
			})
		}
	})

	// Start observing
	slotObserver.value.observe(slotContainer.value, {
		childList: true,
		subtree: false,
		attributes: false,
		characterData: false
	})
}

// Modify updateSlider to safely handle DOM references and always use slotContainer
function updateSlider(): void {
	if (!carouselRef.value || !slotContainer.value || isBuilding.value || !isMounted.value) { return }

	// Prevent too many empty rebuild attempts
	if (emptyRebuildAttempts.value >= MAX_EMPTY_REBUILD_ATTEMPTS) {
		console.warn('Too many rebuild attempts with no items, stopping')
		return
	}

	try {
		// Temporarily disconnect observer to avoid recursive calls
		if (slotObserver.value) {
			slotObserver.value.disconnect()
		}

		// Clone the slot content always
		const slotElements = Array.from(slotContainer.value.children)
			.map(el => el.cloneNode(true) as Element)

		// Only proceed if we have elements to work with or if we need to clear for the first time
		if (slotElements.length > 0 || (innerElements.value.length > 0 && emptyRebuildAttempts.value === 0)) {
			// Update elements with clones of slot content
			innerElements.value = slotElements

			// Adjust current slide if needed
			if (currentSlide.value >= innerElements.value.length) {
				currentSlide.value = Math.max(0, innerElements.value.length - 1)
			}
			// Stop autoplay during rebuild to prevent errors
			const wasAutoplay = config.value.autoplay !== 'disabled'
			if (wasAutoplay) {
				stopAutoplay()
			}

			// Rebuild the slider frame
			buildSliderFrame()

			// Resume autoplay if it was active and not hovering
			if (wasAutoplay && !isHovering.value && innerElements.value.length > 0) {
				// Small delay for stability
				setTimeout(() => {
					startAutoplay()
				}, 50)
			}
		}
	} catch (error) {
		console.error('Error updating slider:', error)
	} finally {
		// Always reconnect observer to the slot container
		if (slotObserver.value && slotContainer.value) {
			slotObserver.value.observe(slotContainer.value, {
				childList: true,
				subtree: false,
				attributes: false,
				characterData: false
			})
		}
	}
}

// Update resetSlider to use slotContainer instead of carouselRef
function resetSlider(): void {
	// Reset the counter to allow rebuilding again
	emptyRebuildAttempts.value = 0

	// Check if we have content now
	if (slotContainer.value) {
		const slotElements = Array.from(slotContainer.value.children)
			.map(el => el.cloneNode(true) as Element)

		if (slotElements.length > 0) {
			// We have content now, rebuild
			innerElements.value = slotElements
			nextTick(() => { buildSliderFrame() })
		}
	}
}

// Expose public methods
defineExpose({
	prev,
	next,
	goTo,
	remove,
	insert,
	prepend,
	append,
	destroy,
	currentSlide,
	pauseAutoplay,
	resumeAutoplay,
	updateSlider,
	resetSlider,
})
</script>

<template>
	<div class="carousel-wrapper">
		<!-- Hidden container to hold the original slot content -->
		<div ref="slotContainer" class="carousel-slot-container" style="display: none;">
			<slot />
		</div>

		<!-- Visible carousel container that we will manually populate -->
		<div ref="carouselRef" class="carousel-container">
			<!-- We'll populate this with cloned content programmatically -->
		</div>
		<!-- Dots navigation (Vue-managed) -->
		<div v-if="props.dots && totalDots > 1" class="carousel-dots">
			<button
				v-for="i in totalDots" :key="i" type="button" class="carousel-dot"
				:class="[{ active: (i - 1) === currentSlide }]" :aria-label="`Go to slide ${i}`" @click="goTo(i - 1)"
			/>
		</div>
	</div>
</template>

<style scoped>
.carousel-wrapper {
	position: relative;
	width: 100%;
}

.carousel-container {
	margin: 0 auto;
	overflow: hidden;
}

.carousel-dots {
	display: flex;
	justify-content: center;
	gap: 8px;
	margin-top: 16px;
}

.carousel-dot {
	width: 12px;
	height: 12px;
	border-radius: 50px;
	background-color: var(--bgl-gray-light);
	border: none;
	padding: 0;
	cursor: pointer;
	transition: all 0.3s ease-in-out;
}

.carousel-dot.active {
	background-color: var(--bgl-primary);
	width: 26px;
}
</style>
