<script setup lang="ts">
import { sleep, useDevice } from '@bagelink/vue'
import { ref, onMounted, onBeforeUnmount, watch, computed, nextTick } 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 }
	startIndex?: number
	draggable?: boolean
	multipleDrag?: boolean
	threshold?: number
	loop?: boolean
	rtl?: boolean
	autoplay?: AutoplayValue
	autoplayInterval?: number
	autoplaySpeed?: number
	pauseOnHover?: boolean
	dots?: boolean
	slideWidth?: number
	slideGap?: number
	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)

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

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

// Lifecycle hooks
onMounted(() => init())

onBeforeUnmount(() => {
	stopAutoplay()
	detachEvents()
})

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

	transformProperty.value = webkitOrNot()

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

	// Create references
	innerElements.value = Array.from(carouselRef.value.children)

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

	await nextTick()

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

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

	const widthItem = selectorWidth.value / perPage.value
	const itemsToBuild = config.value.loop
		? innerElements.value.length + (2 * perPage.value)
		: innerElements.value.length

	// Create frame
	if (!sliderFrame.value) {
		sliderFrame.value = document.createElement('div')
	}

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

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

	// Create document fragment for slides
	const docFragment = document.createDocumentFragment()

	// Add clones before original elements if loop is true
	if (config.value.loop && innerElements.value.length > 0) {
		for (let i = innerElements.value.length - perPage.value; i < innerElements.value.length; i++) {
			// Ensure the index is valid
			if (i >= 0 && i < innerElements.value.length) {
				const element = buildSliderFrameItem(innerElements.value[i].cloneNode(true) as Element)
				docFragment.appendChild(element)
			}
		}
	}

	// Add original elements
	for (let i = 0; i < innerElements.value.length; i++) {
		const element = buildSliderFrameItem(innerElements.value[i])
		docFragment.appendChild(element)
	}

	// Add clones after original elements if loop is true
	if (config.value.loop && innerElements.value.length > 0) {
		for (let i = 0; i < perPage.value; i++) {
			// Ensure the index is valid
			if (i >= 0 && i < innerElements.value.length) {
				const element = buildSliderFrameItem(innerElements.value[i].cloneNode(true) as Element)
				docFragment.appendChild(element)
			}
		}
	}

	// Clear selector and append the frame
	if (sliderFrame.value) {
		sliderFrame.value.innerHTML = ''
		sliderFrame.value.appendChild(docFragment)

		if (carouselRef.value) {
			carouselRef.value.innerHTML = ''
			carouselRef.value.appendChild(sliderFrame.value)
		}
	}

	// Go to current slide
	slideToCurrent()
}

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'
	const percentage = config.value.loop
		? 100 / (innerElements.value.length + (perPage.value * 2))
		: 100 / innerElements.value.length

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

	return elementContainer
}

function slideToCurrent(enableTransitionFlag?: boolean): void {
	const currentSlideValue = config.value.loop
		? currentSlide.value + perPage.value
		: currentSlide.value

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

	if (enableTransitionFlag && sliderFrame.value) {
		// Use requestAnimationFrame for smooth transitions
		requestAnimationFrame(() => {
			requestAnimationFrame(() => {
				enableTransition()
				if (sliderFrame.value) {
					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)`
	}
}

function prev(howManySlides: number = 1): void {
	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 = 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 {
	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 = 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 {
	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 {
	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
	}
}

function touchendHandler(e: TouchEvent): void {
	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') {
		startAutoplay()
	}
}

function touchmoveHandler(e: TouchEvent): void {
	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 {
	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
	}
}

function mouseupHandler(e: MouseEvent): void {
	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) {
		startAutoplay()
	}
}

function mousemoveHandler(e: MouseEvent): void {
	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 (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 (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 {
	// 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> {
	stopAutoplay() // Clear any existing timers first

	if (config.value.autoplay === 'standard') {
		autoplayTimer.value = window.setInterval(() => {
			if (!isHovering.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 {
	// Reset the linear offset
	linearOffset.value = 0
	let lastTimestamp = 0

	// Animation function
	const animate = (timestamp: number) => {
		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 itemWidth = selectorWidth.value / perPage.value
		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()
				return
			}
		}

		// Apply the offset using transforms
		if (sliderFrame.value) {
			const direction = config.value.rtl ? 1 : -1
			sliderFrame.value.style.transition = 'none' // Smooth scrolling, no easing
			sliderFrame.value.style[transformProperty.value as any]
				= `translate3d(${direction * linearOffset.value}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
		linearAnimationFrame.value = requestAnimationFrame(animate)
	}

	// Start the animation
	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()
	}
}

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

<template>
	<div class="carousel-wrapper">
		<div ref="carouselRef" class="carousel-container">
			<slot />
		</div>
		<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>
