<script lang="ts" setup>
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import { useDrawerStack } from './useDrawerStack'

const { drawerStack, isAnyResizing, allocateId } = useDrawerStack()

interface Props {
	visible: boolean
	defaultWidth?: number
	minWidth?: number
	maxWidthRatio?: number
	top?: number
	right?: number
	bottom?: number
	resizable?: boolean
	zIndex?: number
	stack?: boolean
	stackGap?: number
}

const props = withDefaults(defineProps<Props>(), {
	visible: false,
	defaultWidth: undefined,
	minWidth: 420,
	maxWidthRatio: 0.6,
	top: 60,
	right: 5,
	bottom: 10,
	resizable: true,
	zIndex: 1000,
	stack: true,
	stackGap: 14
})

const drawerId = allocateId()
const viewportWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1440)
const drawerWidth = ref(props.defaultWidth || props.minWidth)
const resizeState = reactive({
	startX: 0,
	startWidth: props.minWidth,
	isDragging: false
})

const maxDrawerWidth = computed(() => {
	return Math.max(props.minWidth, Math.floor(viewportWidth.value * props.maxWidthRatio))
})

const clampDrawerWidth = (width: number) => {
	return Math.min(Math.max(width, props.minWidth), maxDrawerWidth.value)
}

const stackItem = computed(() => drawerStack.find((item) => item.id === drawerId))

/**
 * 计算当前抽屉的 right 偏移量（从右到左平铺）
 * 排序规则：zIndex 高的在最右侧，相同 zIndex 时 order 大的（后打开的）在最右侧
 * 每个抽屉的 right = 基础 right + 右侧所有可见抽屉的 (width + gap) 之和
 */
const stackedRight = computed(() => {
	if (!props.stack) return props.right

	const visibleDrawers = drawerStack
		.filter((item) => item.visible)
		.sort((a, b) => b.zIndex - a.zIndex || b.order - a.order)

	const currentIndex = visibleDrawers.findIndex((item) => item.id === drawerId)
	if (currentIndex <= 0) return props.right

	// 累加右侧所有可见抽屉的宽度 + 间距
	return visibleDrawers.slice(0, currentIndex).reduce((offset, item) => {
		return offset + item.width + item.stackGap
	}, props.right)
})

/** 关闭状态的 translateX，确保完全移出视口 */
const closedTranslateX = computed(() => {
	return `calc(100% + ${stackedRight.value}px + 20px)`
})

const rootStyle = computed(() => ({
	top: `${props.top}px`,
	right: `${stackedRight.value}px`,
	bottom: `${props.bottom}px`,
	width: `${drawerWidth.value}px`,
	minWidth: `${props.minWidth}px`,
	maxWidth: `${Math.floor(props.maxWidthRatio * 100)}vw`,
	zIndex: `${props.zIndex}`,
	'--drawer-closed-x': closedTranslateX.value
}))

const syncViewportWidth = () => {
	viewportWidth.value = window.innerWidth
	drawerWidth.value = clampDrawerWidth(drawerWidth.value)
}

const stopResize = () => {
	if (!resizeState.isDragging) return
	resizeState.isDragging = false
	isAnyResizing.value = false
	document.body.style.userSelect = ''
	document.body.style.cursor = ''
	window.removeEventListener('mousemove', onResize)
	window.removeEventListener('mouseup', stopResize)
}

const onResize = (event: MouseEvent) => {
	if (!resizeState.isDragging) return
	const nextWidth = resizeState.startWidth + (resizeState.startX - event.clientX)
	drawerWidth.value = clampDrawerWidth(nextWidth)
}

const onResizeStart = (event: MouseEvent) => {
	if (!props.resizable) return
	resizeState.startX = event.clientX
	resizeState.startWidth = drawerWidth.value
	resizeState.isDragging = true
	isAnyResizing.value = true
	document.body.style.userSelect = 'none'
	document.body.style.cursor = 'ew-resize'
	window.addEventListener('mousemove', onResize)
	window.addEventListener('mouseup', stopResize)
}

const syncStackItem = () => {
	const item = stackItem.value
	if (!item) return
	item.visible = props.visible
	item.width = drawerWidth.value
	item.zIndex = props.zIndex
	item.right = props.right
	item.stackGap = props.stackGap
}

watch(
	() => [props.minWidth, props.maxWidthRatio, props.defaultWidth],
	() => {
		drawerWidth.value = clampDrawerWidth(drawerWidth.value || props.defaultWidth || props.minWidth)
	},
	{ immediate: true }
)

watch(
	() => [props.visible, props.zIndex, props.right, props.stackGap, drawerWidth.value],
	() => {
		syncStackItem()
	},
	{ immediate: true }
)

onMounted(() => {
	drawerStack.push({
		id: drawerId,
		visible: props.visible,
		width: drawerWidth.value,
		zIndex: props.zIndex,
		right: props.right,
		order: drawerId,
		stackGap: props.stackGap
	})
	syncStackItem()
	window.addEventListener('resize', syncViewportWidth)
	syncViewportWidth()
})

onBeforeUnmount(() => {
	stopResize()
	const index = drawerStack.findIndex((item) => item.id === drawerId)
	if (index !== -1) {
		drawerStack.splice(index, 1)
	}
	window.removeEventListener('resize', syncViewportWidth)
})

defineExpose({
	setWidth: (width: number) => {
		drawerWidth.value = clampDrawerWidth(width)
	},
	getWidth: () => drawerWidth.value
})
</script>

<template>
	<div
		class="resizable-drawer shadow-2xl"
		:class="{
			'resizable-drawer--open': props.visible,
			'resizable-drawer--resizing': isAnyResizing
		}"
		:style="rootStyle"
	>
		<div
			v-if="props.resizable"
			class="resizable-drawer__handle"
			@mousedown.prevent="onResizeStart"
		></div>
		<slot />
	</div>
</template>

<style lang="less" scoped>
.resizable-drawer {
	position: fixed;
	background: var(--bg-base);
	border-radius: 8px;
	display: flex;
	flex-direction: column;
	border: 1px solid var(--border-light);
	transform: translateX(var(--drawer-closed-x, calc(100% + 20px)));
	transition: transform 0.25s ease;

	&.resizable-drawer--resizing {
		transition: none;
	}
}

.resizable-drawer--open {
	transform: translateX(0);
}

.resizable-drawer__handle {
	position: absolute;
	left: -8px;
	top: 50%;
	transform: translateY(-50%);
	width: 3px;
	height: 32px;
	background-color: var(--text-secondary);
	border-radius: 8px;
	cursor: ew-resize;

	&:hover {
		background-color: var(--text-primary);
	}
}
</style>
