<script lang="ts" setup>
import { computed, ref, watch, defineAsyncComponent, type Component } from 'vue'
import { useNavigationStore } from '@/stores/navigation'
import { pages, type StackRootNames } from '@/router'
import { useEnvironmentStore } from '@/stores/environment'

const props = defineProps<{ tab: StackRootNames }>()
const nav = useNavigationStore()
const env = useEnvironmentStore()

const stack = computed(() => nav.stacks[props.tab])

const registry: Record<string, Component> = pages.reduce(
	(acc, page) => {
		const comp = page.component
		acc[page.name] =
			typeof comp === 'function'
				? defineAsyncComponent({
						loader: comp as unknown as () => Promise<Component>,
					})
				: comp

		return acc
	},
	{} as Record<string, Component>,
)

const isAnimating = ref(false)
const animationType = ref<'push' | 'pop' | null>(null)
const previousStackLength = ref(stack.value?.length || 0)

const visualTopIndex = computed(() => {
	if (nav.pendingPop?.tab === props.tab) return stack.value?.length ? stack.value.length - 2 : 0
	return stack.value?.length ? stack.value.length - 1 : 0
})

const shouldAnimate = computed(() => {
	return env.isMobile && !env.prefersReducedMotion
})

watch(
	() => stack.value?.length,
	(newLength, oldLength) => {
		if (!newLength || !oldLength) return

		if (nav.activeTab !== props.tab) {
			previousStackLength.value = newLength
			return
		}

		if (newLength > oldLength && shouldAnimate.value) {
			animationType.value = 'push'
			isAnimating.value = true

			setTimeout(() => {
				isAnimating.value = false
				animationType.value = null
			}, 300)
		}

		previousStackLength.value = newLength
	},
)

watch(
	() => nav.pendingPop,
	(pendingPop) => {
		if (!pendingPop || pendingPop.tab !== props.tab || nav.activeTab !== props.tab) {
			return
		}

		if (!shouldAnimate.value) {
			nav.completePop()
			return
		}

		animationType.value = 'pop'
		isAnimating.value = true

		setTimeout(() => {
			isAnimating.value = false
			animationType.value = null
			nav.completePop()
		}, 300)
	},
	{ immediate: true },
)
</script>

<template>
	<div
		:data-tab="props.tab"
		:id="`tabpanel-${props.tab}`"
		:aria-hidden="nav.activeTab !== props.tab"
		:aria-labelledby="`tab-${props.tab}`"
		class="tab-stack"
		aria-role="tabpanel"
	>
		<template v-if="stack">
			<div
				v-for="(entry, index) in stack"
				:key="entry.id"
				:class="[
					'stack-page',
					{
						'is-visible':
							index === stack.length - 1 ||
							index === visualTopIndex ||
							index === visualTopIndex - 1,

						'is-visual-top': index === visualTopIndex,
						'is-below-visual-top': index === visualTopIndex - 1,
						'is-animating': isAnimating,
						'push-enter': isAnimating && animationType === 'push' && index === stack.length - 1,
						'pop-exit': isAnimating && animationType === 'pop' && index === stack.length - 1,
					},
				]"
				:data-entry-id="entry.id"
				:data-index="index"
				:inert="index !== visualTopIndex"
			>
				<Suspense>
					<template #default>
						<component :is="registry[entry.page]" v-bind="entry.props" :routeName="entry.page" />
					</template>
					<template #fallback>
						<div class="page-loading" aria-hidden="true"></div>
					</template>
				</Suspense>
			</div>
		</template>
	</div>
</template>

<style scoped>
.tab-stack {
	position: absolute;
	inset: 0;
}

.stack-page {
	position: absolute;
	inset: 0;
	box-sizing: border-box;

	display: none;
	transform: translateY(100%);
	transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);

	&.no-motion {
		display: block;
		transform: none !important;
		animation: none !important;
		transition: none !important;
	}

	&.is-visible {
		display: block;
	}

	&.is-visual-top {
		transform: translateY(0);
		z-index: 10;
	}

	&.is-below-visual-top {
		transform: translateY(0);
		z-index: 9;
	}

	&.push-enter {
		transform: translateY(9%);
		z-index: 11;
		animation: slideUp 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
	}

	&.pop-exit {
		transform: translateY(0);
		z-index: 11;
		animation: slideDown 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
	}
}

@keyframes slideUp {
	from {
		transform: translateY(20%);
		opacity: 0;
	}
	to {
		transform: translateY(0);
		opacity: 1;
	}
}

@keyframes slideDown {
	from {
		transform: translateY(0);
		opacity: 1;
	}
	to {
		transform: translateY(20%);
		opacity: 0;
	}
}

@media (prefers-reduced-motion: reduce) {
	.stack-page {
		transition: none;
	}

	.stack-page.push-enter,
	.stack-page.pop-exit {
		animation: none;
		transform: translateY(0);
		opacity: 1;
	}
}
</style>
