<!-- prettier-ignore-start -->
<!-- eslint-disable -->
<script setup lang="ts">
import type { IconType, ThemeType } from '@bagelink/vue'
import { Btn, useDevice } from '@bagelink/vue'
import { autoPlacement, computePosition, flip, offset, shift } from '@floating-ui/dom'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'

export type Side = 'top' | 'right' | 'bottom' | 'left'
export type Alignment = 'start' | 'end'
export type AlignedPlacement = 'auto' | Side | `${Side}-${Alignment}`
export type TriggerEvent = 'click' | 'hover'

defineOptions({ inheritAttrs: false })

const props = withDefaults(defineProps<{
	value?: string
	thin?: boolean
	flat?: boolean
	disabled?: boolean
	icon?: IconType
	color?: ThemeType
	triggerClass?: string
	iconEnd?: IconType
	border?: boolean
	outline?: boolean
	round?: boolean
	placement?: AlignedPlacement
	disablePlacement?: boolean
	noAutoFocus?: boolean
	autoHide?: boolean
	triggers?: TriggerEvent[]
	showTriggers?: TriggerEvent[]
	hideTriggers?: TriggerEvent[]
	popperTriggers?: TriggerEvent[]
	popperShowTriggers?: TriggerEvent[]
	popperHideTriggers?: TriggerEvent[]
	showGroup?: string
	card?: boolean
	delay?: number | { show: number; hide: number }
}>(), {
	thin: true,
	flat: true,
	card: true,
	placement: 'bottom-start',
	disablePlacement: false,
	autoHide: true,
	triggers: () => ['click'],
	popperTriggers: () => [],
	popperShowTriggers: () => [],
	popperHideTriggers: () => [],
})

const emit = defineEmits<{ show: []; hide: [] }>()

const shown = defineModel('shown', { type: Boolean, default: false })

const triggerWrapRef = ref<HTMLElement | null>(null)
const popoverRef = ref<HTMLElement | null>(null)
const teleportTarget = ref<string | Element>('body')

const { isMobile, innerWidth } = useDevice()

const shouldDisablePositioning = computed(() => props.disablePlacement && isMobile.value)
const isSmallScreen = computed(() => innerWidth.value < 910)

const effectiveTriggers = computed(() =>
	isSmallScreen.value
		? props.triggers.map(t => (t === 'hover' ? 'click' : t))
		: props.triggers
)
const isClickTrigger = computed(() => effectiveTriggers.value.includes('click'))
const isHoverTrigger = computed(() => effectiveTriggers.value.includes('hover'))

const isPopperHoverTrigger = computed(() => {
	if (props.popperTriggers.length > 0) return props.popperTriggers.includes('hover')
	return props.triggers.includes('hover') && !isSmallScreen.value
})

const iconSet = computed(() => {
	if (props.icon !== undefined && props.icon !== '') return props.icon
	if (props.value === undefined || props.value === '') return 'more_vert'
	return undefined
})

const resolvedDelay = computed((): { show: number; hide: number } => {
	if (props.delay !== undefined) {
		return typeof props.delay === 'number'
			? { show: props.delay, hide: props.delay }
			: props.delay
	}
	if (props.triggers.includes('hover') && !isSmallScreen.value) return { show: 0, hide: 10 }
	return { show: 0, hide: 0 }
})

// ShowGroup registry — shared across all Dropdown instances on the page
const groupRegistry = new Map<string, Set<() => void>>()

async function updatePosition() {
	const triggerEl = triggerWrapRef.value?.firstElementChild as HTMLElement | null
	if (!popoverRef.value || !triggerEl || shouldDisablePositioning.value) return

	const isAuto = props.placement === 'auto'
	const { x, y } = await computePosition(triggerEl, popoverRef.value, {
		placement: isAuto ? undefined : (props.placement as any),
		strategy: 'fixed',
		middleware: [
			offset(4),
			...(isAuto ? [autoPlacement()] : [flip()]),
			shift({ padding: 8 }),
		],
	})

	if (popoverRef.value) {
		popoverRef.value.style.left = `${x}px`
		popoverRef.value.style.top = `${y}px`
	}
}

function isOpen(): boolean {
	return !!popoverRef.value?.matches(':popover-open')
}

async function internalShow() {
	if (props.disabled || isOpen()) return

	teleportTarget.value = triggerWrapRef.value?.closest('dialog[open]') ?? 'body'

	if (props.showGroup && groupRegistry.has(props.showGroup)) {
		groupRegistry.get(props.showGroup)!.forEach(fn => fn())
	}

	// Wait for Teleport to move the element to the new target before promoting to top layer
	await nextTick()
	if (!popoverRef.value) return
	// Show hidden first so floating-ui can measure it, then position, then reveal
	popoverRef.value.style.opacity = '0'
	popoverRef.value.showPopover()
	await updatePosition()
	popoverRef.value.style.opacity = ''
}

function internalHide() {
	if (!isOpen()) return
	if (!props.noAutoFocus) {
		const focusable = triggerWrapRef.value?.querySelector<HTMLElement>(
			'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
		)
		focusable?.focus()
	}
	popoverRef.value?.hidePopover()
}

const show = internalShow
function hide() { internalHide() }

// Sync external shown model → popover state
watch(shown, (val) => {
	if (val && !isOpen()) internalShow()
	else if (!val && isOpen()) internalHide()
})

// Sync popover state → shown model
function onPopoverToggle(e: Event) {
	const te = e as ToggleEvent
	if (te.newState === 'open') {
		shown.value = true
		nextTick(updatePosition)
		emit('show')
	} else {
		shown.value = false
		emit('hide')
	}
}

// Click-outside to auto-hide
function onDocumentPointerDown(e: PointerEvent) {
	if (!props.autoHide || !isOpen()) return
	const target = e.target as Node
	if (popoverRef.value?.contains(target) || triggerWrapRef.value?.contains(target)) return
	internalHide()
}

// Esc key
function onKeydown(e: KeyboardEvent) {
	if (e.key === 'Escape' && isOpen()) {
		e.stopPropagation()
		internalHide()
	}
}

// Hover delay timers
let showTimer: ReturnType<typeof setTimeout> | undefined
let hideTimer: ReturnType<typeof setTimeout> | undefined
function clearTimers() { clearTimeout(showTimer); clearTimeout(hideTimer) }

function onTriggerClick() {
	if (!isClickTrigger.value) return
	isOpen() ? internalHide() : internalShow()
}
function onTriggerMouseenter() {
	if (!isHoverTrigger.value) return
	clearTimeout(hideTimer)
	showTimer = setTimeout(internalShow, resolvedDelay.value.show)
}
function onTriggerMouseleave() {
	if (!isHoverTrigger.value) return
	clearTimeout(showTimer)
	hideTimer = setTimeout(internalHide, resolvedDelay.value.hide)
}
function onPopoverMouseenter() {
	if (!isPopperHoverTrigger.value) return
	clearTimeout(hideTimer)
}
function onPopoverMouseleave() {
	if (!isPopperHoverTrigger.value) return
	hideTimer = setTimeout(internalHide, resolvedDelay.value.hide)
}

function onScroll() {
	if (isOpen()) updatePosition()
}

onMounted(() => {
	document.addEventListener('pointerdown', onDocumentPointerDown, true)
	document.addEventListener('keydown', onKeydown)
	document.addEventListener('scroll', onScroll, { capture: true, passive: true })
	if (props.showGroup) {
		if (!groupRegistry.has(props.showGroup)) groupRegistry.set(props.showGroup, new Set())
		groupRegistry.get(props.showGroup)!.add(internalHide)
	}
})

onBeforeUnmount(() => {
	document.removeEventListener('pointerdown', onDocumentPointerDown, true)
	document.removeEventListener('keydown', onKeydown)
	document.removeEventListener('scroll', onScroll, true)
	clearTimers()
	if (props.showGroup) groupRegistry.get(props.showGroup)?.delete(internalHide)
	if (isOpen()) popoverRef.value?.hidePopover()
})

defineExpose({ show, hide, shown })
</script>

<template>
	<div style="display: contents">
		<!-- Trigger wrapper — click/hover only apply to the default Btn trigger -->
		<div
			ref="triggerWrapRef" v-bind="$attrs"
			@mouseenter="onTriggerMouseenter" @mouseleave="onTriggerMouseleave" @focus="onTriggerMouseenter"
			@blur="onTriggerMouseleave"
		>
			<slot name="trigger" :show :hide :shown>
				<Btn
					:class="triggerClass" :iconEnd :icon="iconSet" :value :thin :flat :outline :round :color
					:disabled @click="onTriggerClick"
				/>
			</slot>
		</div>

		<!-- Popup — teleported to closest dialog[open] or body, promoted to top layer via popover -->
		<Teleport :to="teleportTarget">
			<div
				ref="popoverRef" popover="manual" class="bgl-dropdown m-0 p-0 border-none"
				style="border-radius: var(--card-border-radius);"
				:class="{ 'bgl-dropdown--no-positioning': shouldDisablePositioning, 'bg-white shadow': props.card }"
				@toggle="onPopoverToggle" @mouseenter="onPopoverMouseenter" @mouseleave="onPopoverMouseleave"
				@focus="onPopoverMouseenter" @blur="onPopoverMouseleave"
			>
				<div class="bgl-dropdown__backdrop" />
				<div class="bgl-dropdown__content" :class="{ 'display-flex column': props.card }">
					<slot :hide :show :shown />
				</div>
			</div>
		</Teleport>
	</div>
</template>

<style>
/* Reset browser UA popover styles */
.bgl-dropdown {
	margin: 0;
	padding: 0;
	border: none;
	background: transparent;
	overflow: visible;
	position: fixed;
	inset: unset;
	top: 0;
	left: 0;
}

/* Mobile full-screen slide-up mode */
.bgl-dropdown.bgl-dropdown--no-positioning {
	inset: 0;
	width: 100%;
	height: 100%;
	display: flex;
	align-items: flex-end;
	overflow: hidden;
}

.bgl-dropdown.bgl-dropdown--no-positioning .bgl-dropdown__backdrop {
	position: absolute;
	inset: 0;
	background: rgba(0 0 0 / 90%);
}

.bgl-dropdown.bgl-dropdown--no-positioning .bgl-dropdown__content {
	width: 100%;
	position: relative;
	transform: translateY(0);
}

@starting-style {
	.bgl-dropdown.bgl-dropdown--no-positioning:popover-open .bgl-dropdown__content {
		transform: translateY(100%);
	}
}

.bgl-dropdown.bgl-dropdown--no-positioning .bgl-dropdown__content {
	transition: transform 0.15s ease-out;
}

/* Hide backdrop in normal mode */
.bgl-dropdown:not(.bgl-dropdown--no-positioning) .bgl-dropdown__backdrop {
	display: none;
}

/* Desktop open animation */
@keyframes bgl-dropdown-enter {
	from {
		opacity: 0;
		transform: translateY(-4px) scaleY(0.95);
	}

	to {
		opacity: 1;
		transform: translateY(0) scaleY(1);
	}
}

.bgl-dropdown:not(.bgl-dropdown--no-positioning):popover-open {
	animation: bgl-dropdown-enter 0.18s ease 0.04s both;
	transform-origin: top left;
}
</style>
<!-- prettier-ignore-end -->
<!-- eslint-enable -->
