<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, nextTick } from 'vue'
import { IconCloseRounded } from '@iconify-prerendered/vue-material-symbols'
import { useEnvironmentStore } from '@/stores/environment'
import { useOverlayInteractions } from '@/composables/useOverlayInteractions'

defineProps<{
	title?: string
	width?: string
	zIndex?: number
}>()

const emit = defineEmits<{
	(e: 'close'): void
}>()

const env = useEnvironmentStore()
const isMobile = computed(() => env.isMobile)

const modalContainerRef = ref<HTMLElement | null>(null)

const { isDragging, currentY, backdropOpacity, onTouchStart, onTouchMove, onTouchEnd } =
	useOverlayInteractions({
		isMobile,
		containerRef: modalContainerRef,
		onClose: () => emit('close'),
		focusFirstSelector: undefined,
		allowDragFrom: (target) => !target.closest('.modal-body') && !target.closest('.modal-footer'),
		closeThreshold: 150,
		opacityDistance: 400,
	})

const getFocusableElements = (): HTMLElement[] => {
	if (!modalContainerRef.value) return []
	return Array.from(
		modalContainerRef.value.querySelectorAll(
			'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
		),
	) as HTMLElement[]
}

const trapFocus = (e: KeyboardEvent) => {
	if (!modalContainerRef.value) return
	const focusableContent = getFocusableElements()
	if (focusableContent.length === 0) return

	const firstElement = focusableContent[0]
	const lastElement = focusableContent[focusableContent.length - 1]

	if (e.shiftKey) {
		if (document.activeElement === firstElement) {
			lastElement?.focus()
			e.preventDefault()
		}
	} else {
		if (document.activeElement === lastElement) {
			firstElement?.focus()
			e.preventDefault()
		}
	}
}

const handleKeydown = (e: KeyboardEvent) => {
	if (e.key === 'Escape') {
		emit('close')
	}
	if (e.key === 'Tab') {
		trapFocus(e)
	}
}

onMounted(async () => {
	document.addEventListener('keydown', handleKeydown)
	await nextTick()

	if (modalContainerRef.value) {
		const focusable = getFocusableElements()
		if (focusable.length > 0) {
			const firstContentFocus = focusable.find((el) => !el.classList.contains('close-btn'))
			const elementToFocus = firstContentFocus || focusable[0]
			elementToFocus?.focus()
		} else {
			modalContainerRef.value.focus()
		}
	}
})

onUnmounted(() => {
	document.removeEventListener('keydown', handleKeydown)
})
</script>

<template>
	<div
		class="modal-wrapper"
		:style="{ zIndex: zIndex || 9999 }"
		role="dialog"
		aria-modal="true"
		:aria-labelledby="title ? 'modal-title-id' : undefined"
	>
		<div
			class="backdrop"
			@click="emit('close')"
			aria-hidden="true"
			:style="{
				opacity: isDragging ? backdropOpacity : undefined,
			}"
			:class="{ 'is-dragging': isDragging }"
		></div>

		<div
			ref="modalContainerRef"
			class="modal-container"
			:class="{ 'is-mobile': isMobile, 'is-desktop': !isMobile }"
			role="dialog"
			aria-modal="true"
			:aria-labelledby="title ? 'modal-title-id' : undefined"
			tabindex="-1"
			:style="{ zIndex: (zIndex || 9999) + 1 }"
			@click.self="emit('close')"
		>
			<div
				class="modal-content"
				:style="{
					maxWidth: width || '768px',
					transform: isMobile && currentY > 0 ? `translateY(${currentY}px)` : undefined,
					transition: isDragging ? 'none' : undefined,
				}"
				@click.stop
				@touchstart="onTouchStart"
				@touchmove="onTouchMove"
				@touchend="onTouchEnd"
			>
				<div v-if="isMobile" class="drag-handle-wrapper">
					<div class="drag-handle" aria-hidden="true"></div>
				</div>

				<div class="modal-header">
					<h2 v-if="title" id="modal-title-id" class="modal-title">{{ title }}</h2>

					<button class="close-btn" @click="emit('close')" aria-label="Close modal" type="button">
						<IconCloseRounded />
					</button>
				</div>

				<div class="modal-body">
					<slot />
				</div>

				<div v-if="$slots.footer" class="modal-footer">
					<slot name="footer" />
				</div>
			</div>
		</div>
	</div>
</template>

<style scoped lang="scss">
@use '@/assets/variables' as *;

.modal-wrapper,
.backdrop,
.modal-container,
.modal-content {
	transition-timing-function: $ease-spring;
	transition-duration: 0.3s;
}

.modal-wrapper {
	position: fixed;
	inset: 0;
	display: flex;
	flex-direction: column;
	pointer-events: none;
}

.backdrop {
	position: absolute;
	inset: 0;
	background: hsla(var(--crust) / 0.6);
	backdrop-filter: blur(4px);
	pointer-events: auto;
	&.is-dragging {
		transition: none;
	}
}

.modal-container {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	outline-color: transparent;
	pointer-events: none;
}

.modal-content {
	pointer-events: auto;
	background: hsl(var(--base));
	display: flex;
	flex-direction: column;
	max-height: 90vh;
	width: 100%;
	position: relative;
	box-shadow:
		0 10px 25px -5px rgba(0, 0, 0, 0.1),
		0 8px 10px -6px rgba(0, 0, 0, 0.1);
	will-change: transform;
}

.is-desktop {
	inset: 0;
	align-items: center;
	justify-content: center;
	padding: 1rem;
	pointer-events: auto;

	.modal-header {
		padding-top: 1.25rem;
	}

	.modal-content {
		border-radius: 1rem;
		border: 1px solid hsla(var(--surface2) / 0.2);
	}
}

.is-mobile {
	bottom: 0;
	left: 0;
	right: 0;
	justify-content: flex-end;

	.modal-content {
		border-top-left-radius: 1.5rem;
		border-top-right-radius: 1.5rem;
		padding-bottom: env(safe-area-inset-bottom, 20px);
		max-height: 85vh;
	}
}

.modal-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 0.5rem 1.5rem 0.5rem;
	flex-shrink: 0;

	.modal-title {
		font-size: 1.25rem;
		font-weight: 700;
		color: hsl(var(--text));
		margin: 0;
	}
}

.drag-handle-wrapper {
	width: 100%;
	display: flex;
	justify-content: center;
	padding-top: 0.75rem;
	padding-bottom: 0.25rem;
	touch-action: none;

	.drag-handle {
		width: 3rem;
		height: 0.25rem;
		background: hsl(var(--surface2));
		border-radius: 99px;
	}
}

.close-btn {
	background: transparent;
	border: none;
	font-size: 1.5rem;
	color: hsl(var(--subtext0));
	cursor: pointer;

	width: 2rem;
	height: 2rem;

	display: flex;
	align-items: center;
	justify-content: center;

	padding: 0;
	line-height: 1;

	margin-left: auto;
	border-radius: 0.25rem;

	&:focus-visible,
	&:hover {
		color: hsl(var(--text));
		background: hsla(var(--surface0) / 0.5);
	}
}

.modal-body {
	padding: 0 1.5rem 1.5rem;
	overflow-y: auto;
	flex: 1;
	color: hsl(var(--text));
}

.modal-footer {
	padding: 1rem 1.5rem;
	border-top: 1px solid hsl(var(--surface0));
	display: flex;
	gap: 0.5rem;
	justify-content: flex-end;
}

.fade-enter-active,
.fade-leave-active {
	transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
	opacity: 0;
}

.zoom-enter-active,
.zoom-leave-active {
	transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.zoom-enter-from,
.zoom-leave-to {
	opacity: 0;
	transform: scale(0.95);
}

.slide-up-enter-active,
.slide-up-leave-active {
	transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
}
.slide-up-enter-from,
.slide-up-leave-to {
	transform: translateY(100%);
}
</style>
