<script setup lang="ts">
import { ref, watch, inject, onUnmounted } from 'vue'

// ══════ 全局状态管理  ══════
let _zSeed = 1000
let _activeCount = 0
// 记录所有活动的 overlay 的 z-index，用于判断最顶层
const _activeZIndices = new Set<number>()

const props = withDefaults(defineProps<{
	visible: boolean
	title?: string
	noHeader?: boolean
	/** 点击标题时的回调，传入后标题可点击 */
	titleClick?: () => void
	/** 为 true 时：默认槽在首次打开后才挂载，关闭后保留 DOM，减少未打开 overlay 的重内容渲染 */
	lazy?: boolean
}>(), {
	title: '',
	noHeader: false,
	lazy: false
})

const emit = defineEmits<{
	/** 打开时触发（当 visible 变为 true 时） */
	open: []
	/** 关闭时触发（用户主动关闭时，如点击关闭按钮、返回键等） */
	close: []
	/** 关闭完成时触发（当 visible 变为 false 时，用于生命周期监听） */
	closed: []
}>()

// ══════ 层级 Z-Index（新打开的永远在上层） ══════
const zIndex = ref(_zSeed)

// ══════ 状态栏高度 ══════
const statusBarHeight = ref(0)

// ══════ 返回键栈管理 ══════
const tabRootBackPress = inject<{
	register: (h: { check: () => boolean; close: () => void }) => () => void
}>('tabRootBackPress', undefined as any)

let unreg: (() => void) | null = null

// ══════ 内部状态 ══════
const isTracked = ref(false)
/** lazy 模式下，首次打开后为 true，用于保持 slot 挂载避免重复创建 */
const hasOpenedOnce = ref(false)
/** 关闭动画定时器，用于延迟触发 closed 事件 */
let closeTimer: ReturnType<typeof setTimeout> | null = null

// ══════ 工具函数 ══════
/**
 * 获取状态栏高度
 */
const getStatusBarHeight = (): number => {
	try {
		return uni.getSystemInfoSync().statusBarHeight ?? 0
	} catch {
		return 0
	}
}

/**
 * 注册到动态页面计数
 */
const registerActive = () => {
	if (!isTracked.value) {
		isTracked.value = true
		_activeCount++
		_activeZIndices.add(zIndex.value)
	}
}

/**
 * 从动态页面计数中注销
 */
const unregisterActive = () => {
	if (isTracked.value) {
		isTracked.value = false
		_activeZIndices.delete(zIndex.value)
		if (--_activeCount <= 0) {
			_activeCount = 0
			_zSeed = 1000
			_activeZIndices.clear()
		}
	}
}

/**
 * 获取当前最大的 z-index
 */
const getMaxZIndex = (): number => {
	if (_activeZIndices.size === 0) return 0
	return Math.max(...Array.from(_activeZIndices))
}

/**
 * 注册返回键处理
 * 只有当当前 overlay 可见且是最顶层（z-index 最高）时才响应返回键
 */
const registerBackPress = () => {
	if (tabRootBackPress) {
		unreg = tabRootBackPress.register({
			check: () => {
				// 只有当前 overlay 可见且是最顶层时才返回 true
				if (!props.visible) return false;
				// 检查是否是最顶层：当前 z-index 应该等于所有活动 overlay 中的最大 z-index
				const maxZ = getMaxZIndex()
				return zIndex.value === maxZ && maxZ > 0
			},
			close: handleClose
		})
	}
}

/**
 * 注销返回键处理
 */
const unregisterBackPress = () => {
	if (unreg) {
		unreg()
		unreg = null
	}
}

/**
 * 处理打开逻辑
 */
const handleOpen = () => {
	// 分配新层级
	zIndex.value = ++_zSeed
	// 注册层级
	registerActive()
	// 获取状态栏高度
	statusBarHeight.value = getStatusBarHeight()
	// 注册返回键
	registerBackPress()
	// 触发生命周期事件
	emit('open')
}

/**
 * 处理关闭逻辑
 */
const handleClose = () => {
	emit('close')
}

/**
 * 处理关闭后的清理
 * 延迟触发 closed 事件，等待退出动画完成
 */
const handleCloseCleanup = () => {
	// 注销返回键
	unregisterBackPress()
	// 注销活动计数
	unregisterActive()
	// 清除之前的定时器（如果存在）
	if (closeTimer) {
		clearTimeout(closeTimer)
		closeTimer = null
	}
	// 延迟触发 closed 事件，等待动画完成
	closeTimer = setTimeout(() => {
		closeTimer = null
		emit('closed')
	}, 600)
}

// ══════ 监听 visible 变化，管理生命周期 ══════
watch(() => props.visible, (newVal, oldVal) => {
	if (newVal === oldVal) return

	if (newVal) {
		// 如果正在关闭动画中，取消关闭定时器
		if (closeTimer) {
			clearTimeout(closeTimer)
			closeTimer = null
		}
		if (props.lazy) hasOpenedOnce.value = true
		handleOpen()
	} else {
		handleCloseCleanup()
	}
})

// ══════ 组件卸载时的清理 ══════
onUnmounted(() => {
	unregisterBackPress()
	unregisterActive()
	// 清除关闭动画定时器
	if (closeTimer) {
		clearTimeout(closeTimer)
		closeTimer = null
	}
})

// ══════ 暴露给模板的方法 ══════
const close = handleClose
</script>

<template>
	<view class="ov-page" :class="{ 'ov-page--visible': visible }" :style="{ zIndex }">
		<!-- 遮罩: 屏蔽下层触摸  -->
		<view
			v-show="visible"
			class="ov-page__backdrop"
			:class="{ 'ov-page__backdrop--visible': visible }"
			@touchmove.stop.prevent
			@touchstart.stop
			@touchend.stop
		/>
		<!-- 滑入面板 -->
		<view class="ov-page__panel" :class="{ 'ov-page__panel--in': visible }">
			<!-- 顶栏 (noHeader 关闭 / #header 完全替换 / #header-right 自定义右侧) -->
			<template v-if="!noHeader">
				<slot name="header" :close="close" :statusBarHeight="statusBarHeight">
					<view class="ov-header" :style="{ paddingTop: statusBarHeight + 'px' }">
						<view class="ov-header__inner">
							<view class="ov-header__back" @click="close">
								<wd-icon name="thin-arrow-left" size="32rpx" color="var(--dv-text)" />
							</view>
							<view
								v-if="titleClick"
								class="ov-header__title ov-header__title--clickable"
								@click="titleClick"
							>{{ title }}</view>
							<text v-else class="ov-header__title">{{ title }}</text>
							<view class="ov-header__right">
								<slot name="header-right" />
							</view>
						</view>
					</view>
				</slot>
			</template>
			<!-- 主内容（lazy 时首次打开后才挂载，减少未打开 overlay 的重内容） -->
			<template v-if="!lazy || visible || hasOpenedOnce">
				<slot />
			</template>
		</view>
		<!-- 额外内容插槽（用于放置嵌套 overlay，避免 transform 影响 fixed 定位） -->
		<slot name="extra" />
	</view>
</template>

<style lang="scss" scoped>
.ov-page {
	position: fixed;
	top: 0; left: 0; right: 0; bottom: 0;
	pointer-events: none;
}
.ov-page--visible { pointer-events: auto; }

.ov-page__backdrop {
	position: absolute;
	inset: 0;
	z-index: 0;
	opacity: 0;
	transition: opacity .1s cubic-bezier(.22, .61, .36, 1);
}
.ov-page__backdrop--visible {
	opacity: 1;
	transition: opacity .1s cubic-bezier(.22, .61, .36, 1);
}

.ov-page__panel {
	position: absolute;
	inset: 0;
	z-index: 1;
	display: flex;
	flex-direction: column;
	background: var(--dv-bg);
	transform: translateX(100%);
	opacity: 0;
	filter: blur(20rpx);
	box-shadow: -8rpx 0 24rpx rgba(0, 0, 0, 0.15);
	transition: transform .3s cubic-bezier(.22, .61, .36, 1), opacity .6s cubic-bezier(.22, .61, .36, 1), filter .6s cubic-bezier(.22, .61, .36, 1);
	will-change: transform, opacity, filter;
}
.ov-page__panel--in {
	transform: translateX(0);
	opacity: 1;
	filter: blur(0);
	transition: transform .3s cubic-bezier(.22, .61, .36, 1), opacity .25s cubic-bezier(.22, .61, .36, 1), filter .25s cubic-bezier(.22, .61, .36, 1);
}

.ov-header {
	position: sticky;
	top: 0;
	z-index: 100;
	background: var(--dv-bg);
	padding-left: env(safe-area-inset-left);
	padding-right: env(safe-area-inset-right);
}
.ov-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	height: 88rpx;
	padding: 0 24rpx;
}
.ov-header__back {
	width: 72rpx;
	display: flex;
	align-items: center;
	padding: 8rpx;
}
.ov-header__title {
	flex: 1;
	text-align: center;
	font-size: var(--dv-font-title);
	font-weight: 600;
	color: var(--dv-text);
}
.ov-header__title--clickable {
	cursor: pointer;
}
.ov-header__right {
	width: 88rpx;
	display: flex;
	align-items: center;
	justify-content: flex-end;
}
</style>
