<template>
	<!-- #ifdef H5 -->
	<teleport to="uni-app" :disabled="!enablePortal">
		<!-- #endif -->

		<!-- #ifdef MP -->
		<root-portal :enable="enablePortal">
			<!-- #endif -->

			<view
				class="cl-popup-wrapper"
				:class="[`cl-popup-wrapper--${direction}`]"
				:style="{
					zIndex,
					pointerEvents
				}"
				v-show="visible"
				v-if="keepAlive ? true : visible"
				@touchmove.stop.prevent
			>
				<view
					class="cl-popup-mask"
					:class="[
						{
							'is-open': status == 1,
							'is-close': status == 2
						},
						pt.mask?.className
					]"
					@tap="maskClose"
					v-if="showMask"
				></view>

				<view
					class="cl-popup"
					:class="[
						{
							'is-open': status == 1,
							'is-close': status == 2,
							'is-custom-navbar': router.isCustomNavbarPage(),
							'stop-transition': swipe.isTouch
						},
						pt.className
					]"
					:style="popupStyle"
					@touchstart="onTouchStart"
					@touchmove="onTouchMove"
					@touchend="onTouchEnd"
					@touchcancel="onTouchEnd"
				>
					<view
						class="cl-popup__inner"
						:class="[
							{
								'is-dark': isDark
							},
							pt.inner?.className
						]"
						:style="{
							paddingBottom
						}"
					>
						<view
							class="cl-popup__draw"
							:class="[
								{
									'!bg-surface-400': swipe.isMove
								},
								pt.draw?.className
							]"
							v-if="isSwipeClose"
						></view>

						<view
							class="cl-popup__header"
							:class="[pt.header?.className]"
							v-if="showHeader"
						>
							<slot name="header">
								<cl-text
									ellipsis
									:pt="{
										className: `text-lg font-bold ${pt.header?.text?.className}`
									}"
									>{{ title }}</cl-text
								>
							</slot>

							<cl-icon
								name="close-circle-fill"
								:size="40"
								:pt="{
									className: parseClass([
										'absolute right-[24rpx] text-surface-400',
										[isDark, 'text-surface-50']
									])
								}"
								@tap="close"
								@touchmove.stop
								v-if="isOpen && showClose"
							></cl-icon>
						</view>

						<view
							class="cl-popup__container"
							:class="[pt.container?.className]"
							@touchmove.stop
						>
							<slot></slot>
						</view>
					</view>
				</view>
			</view>

			<!-- #ifdef MP -->
		</root-portal>
		<!-- #endif -->

		<!-- #ifdef H5 -->
	</teleport>
	<!-- #endif -->
</template>

<script lang="ts" setup>
import { computed, reactive, ref, watch, type PropType } from "vue";
import { getSafeAreaHeight, isAppIOS, parseClass, parsePt, parseRpx } from "@/cool";
import type { ClPopupDirection, PassThroughProps } from "../../types";
import { isDark, router } from "@/cool";
import { config } from "../../config";

defineOptions({
	name: "cl-popup"
});

defineSlots<{
	header: () => any;
}>();

// 组件属性定义
const props = defineProps({
	// 透传样式配置
	pt: {
		type: Object,
		default: () => ({})
	},
	// 是否可见
	modelValue: {
		type: Boolean,
		default: false
	},
	// 标题
	title: {
		type: String,
		default: null
	},
	// 弹出方向
	direction: {
		type: String as PropType<ClPopupDirection>,
		default: "bottom"
	},
	// 弹出框宽度
	size: {
		type: [String, Number],
		default: ""
	},
	// 是否显示头部
	showHeader: {
		type: Boolean,
		default: true
	},
	// 显示关闭按钮
	showClose: {
		type: Boolean,
		default: true
	},
	// 是否显示遮罩层
	showMask: {
		type: Boolean,
		default: true
	},
	// 是否点击遮罩层关闭弹窗
	maskClosable: {
		type: Boolean,
		default: true
	},
	// 是否开启拖拽关闭
	swipeClose: {
		type: Boolean,
		default: true
	},
	// 拖拽关闭的阈值
	swipeCloseThreshold: {
		type: Number,
		default: 150
	},
	// 触摸事件响应方式
	pointerEvents: {
		type: String as PropType<"auto" | "none">,
		default: "auto"
	},
	// 是否开启缓存
	keepAlive: {
		type: Boolean,
		default: false
	},
	// 是否启用 portal
	enablePortal: {
		type: Boolean,
		default: true
	}
});

// 定义组件事件
const emit = defineEmits(["update:modelValue", "open", "opened", "close", "closed", "maskClose"]);

// 透传样式类型定义
type HeaderPassThrough = {
	className?: string;
	text?: PassThroughProps;
};

type PassThrough = {
	className?: string;
	inner?: PassThroughProps;
	header?: HeaderPassThrough;
	container?: PassThroughProps;
	mask?: PassThroughProps;
	draw?: PassThroughProps;
};

// 解析透传样式配置
const pt = computed(() => parsePt<PassThrough>(props.pt));

// 控制弹出层显示/隐藏
const visible = ref(false);

// 0: 初始状态 1: 打开中 2: 关闭中
const status = ref(0);

// 标记弹出层是否处于打开状态(包含动画过程)
const isOpen = ref(false);

// 标记弹出层是否已完全打开(动画结束)
const isOpened = ref(false);

// 弹出层z-index值
const zIndex = ref(config.zIndex);

// 计算弹出层高度
const height = computed(() => {
	switch (props.direction) {
		case "top":
		case "bottom":
			return parseRpx(props.size); // 顶部和底部弹出时使用传入的size
		case "left":
		case "right":
			return "100%"; // 左右弹出时占满全高
		default:
			return "";
	}
});

// 计算弹出层宽度
const width = computed(() => {
	switch (props.direction) {
		case "top":
		case "bottom":
			return "100%"; // 顶部和底部弹出时占满全宽
		case "left":
		case "right":
		case "center":
			return parseRpx(props.size); // 其他方向使用传入的size
		default:
			return "";
	}
});

// 底部安全距离
const paddingBottom = computed(() => {
	let h = 0;

	if (props.direction == "bottom") {
		h += getSafeAreaHeight("bottom");
	}

	return h + "px";
});

// 是否显示拖动条
const isSwipeClose = computed(() => {
	return props.direction == "bottom" && props.swipeClose;
});

// 动画定时器
let timer: number = 0;

// 打开弹出层
function open() {
	// 递增z-index,保证多个弹出层次序
	zIndex.value = config.zIndex++;

	if (!visible.value) {
		// 显示弹出层
		visible.value = true;

		// 触发事件
		emit("update:modelValue", true);
		emit("open");

		// 等待DOM更新后开始动画
		setTimeout(
			() => {
				// 设置打开状态
				status.value = 1;

				// 动画结束后触发opened事件
				// @ts-ignore
				timer = setTimeout(() => {
					isOpened.value = true;
					emit("opened");
				}, 350);
			},
			isAppIOS() ? 100 : 50
		);
	}
}

// 关闭弹出层
function close() {
	if (status.value == 1) {
		// 重置打开状态
		isOpened.value = false;

		// 设置关闭状态
		status.value = 2;

		// 触发事件
		emit("close");

		// 清除未完成的定时器
		if (timer != 0) {
			clearTimeout(timer);
		}

		// 动画结束后隐藏弹出层
		// @ts-ignore
		timer = setTimeout(() => {
			// 隐藏弹出层
			visible.value = false;

			// 重置状态
			status.value = 0;

			// 触发事件
			emit("update:modelValue", false);
			emit("closed");
		}, 350);
	}
}

// 点击遮罩层关闭
function maskClose() {
	if (props.maskClosable) {
		close();
	}

	emit("maskClose");
}
// 滑动状态类型定义
type Swipe = {
	isMove: boolean; // 是否移动
	isTouch: boolean; // 是否处于触摸状态
	startY: number; // 开始触摸的Y坐标
	offsetY: number; // Y轴偏移量
};

// 初始化滑动状态数据
const swipe = reactive<Swipe>({
	isMove: false, // 是否移动
	isTouch: false, // 默认非触摸状态
	startY: 0, // 初始Y坐标为0
	offsetY: 0 // 初始偏移量为0
});

/**
 * 触摸开始事件处理
 * @param e 触摸事件对象
 * 当弹出层获得焦点且允许滑动关闭时,记录触摸起始位置
 */
function onTouchStart(e: UniTouchEvent) {
	if (props.direction != "bottom") {
		return;
	}

	if (isOpened.value && isSwipeClose.value) {
		swipe.isTouch = true; // 标记开始触摸
		swipe.startY = e.touches[0].clientY; // 记录起始Y坐标
	}
}

/**
 * 触摸移动事件处理
 * @param e 触摸事件对象
 * 计算手指移动距离,更新弹出层位置
 */
function onTouchMove(e: UniTouchEvent) {
	if (swipe.isTouch) {
		// 标记为移动状态
		swipe.isMove = true;

		// 计算Y轴偏移量
		const offsetY = (e.touches[0] as UniTouch).pageY - swipe.startY;

		// 只允许向下滑动(offsetY > 0)
		if (offsetY > 0) {
			swipe.offsetY = offsetY;
		}
	}
}

/**
 * 触摸结束事件处理
 * 根据滑动距离判断是否关闭弹出层
 */
function onTouchEnd() {
	if (swipe.isTouch) {
		// 结束触摸状态
		swipe.isTouch = false;

		// 结束移动状态
		swipe.isMove = false;

		// 如果滑动距离超过阈值,则关闭弹出层
		if (swipe.offsetY > props.swipeCloseThreshold) {
			close();
		}

		// 重置偏移量
		swipe.offsetY = 0;
	}
}

/**
 * 计算弹出层样式
 * 根据滑动状态动态设置transform属性实现位移动画
 */
const popupStyle = computed(() => {
	const style = {};

	// 基础样式
	style["height"] = height.value;
	style["width"] = width.value;

	// 处于触摸状态时添加位移效果
	if (swipe.isTouch) {
		style["transform"] = `translateY(${swipe.offsetY}px)`;
	}

	return style;
});

// 监听modelValue变化
watch(
	computed(() => props.modelValue),
	(val: boolean) => {
		if (val) {
			open();
		} else {
			close();
		}
	},
	{
		immediate: true
	}
);

// 监听状态变化
watch(status, (val: number) => {
	isOpen.value = val == 1;
});

defineExpose({
	isOpened,
	isOpen,
	open,
	close
});
</script>

<style lang="scss" scoped>
.cl-popup-wrapper {
	@apply h-full w-full;
	@apply fixed top-0 bottom-0 left-0 right-0;
	pointer-events: none;

	.cl-popup-mask {
		@apply absolute top-0 bottom-0 left-0 right-0;
		@apply h-full w-full;
		@apply bg-black opacity-0;
		transition-property: opacity;

		&.is-open {
			@apply opacity-40;
		}

		&.is-open,
		&.is-close {
			transition-duration: 0.3s;
		}
	}

	.cl-popup {
		@apply absolute duration-300;
		transition-property: transform;

		&__inner {
			@apply bg-white h-full w-full flex flex-col;

			&.is-dark {
				@apply bg-surface-700;
			}
		}

		&__draw {
			@apply bg-surface-200 rounded-md;
			@apply absolute top-2 left-1/2;
			height: 10rpx;
			width: 70rpx;
			transform: translateX(-50%);
			transition-property: background-color;
			transition-duration: 0.2s;
		}

		&__header {
			@apply flex flex-row items-center flex-wrap;
			height: 90rpx;
			padding: 0 80rpx 0 26rpx;
		}

		&__container {
			flex: 1;
		}

		&.stop-transition {
			@apply transition-none;
		}
	}

	&--left {
		.cl-popup {
			@apply left-0 top-0;
			transform: translateX(-100%);

			&.is-open {
				transform: translateX(0);
			}
		}
	}

	&--right {
		.cl-popup {
			@apply right-0 top-0;
			transform: translateX(100%);

			&.is-open {
				transform: translateX(0);
			}
		}
	}

	&--top {
		.cl-popup {
			@apply left-0 top-0;
			transform: translateY(-100%);

			.cl-popup__inner {
				@apply rounded-b-2xl;
			}

			&.is-open {
				transform: translateY(0);
			}
		}
	}

	&--left,
	&--right,
	&--top {
		& > .cl-popup {
			// #ifdef H5
			top: 44px;
			// #endif

			&.is-custom-navbar {
				top: 0;
			}
		}
	}

	&--left,
	&--right {
		& > .cl-popup {
			// #ifdef H5
			height: calc(100% - 44px) !important;
			// #endif
		}
	}

	&--bottom {
		& > .cl-popup {
			@apply left-0 bottom-0;
			transform: translateY(100%);

			.cl-popup__inner {
				@apply rounded-t-2xl;
			}

			&.is-open {
				transform: translateY(0);
			}

			&.is-close {
				transform: translateY(100%);
			}
		}
	}

	&--center {
		@apply flex flex-col items-center justify-center;

		& > .cl-popup {
			transform: scale(1.3);
			opacity: 0;
			transition-property: transform, opacity;

			.cl-popup__inner {
				@apply rounded-2xl;
			}

			&.is-open {
				transform: translate(0, 0) scale(1);
				opacity: 1;
			}
		}
	}
}
</style>
