<template>
	<div
		v-show="ctx.open"
		ref="menuRef"
		class="ctxmenu"
		:style="{ left: ctx.left + 'px', top: ctx.top + 'px' }"
		@mousedown.stop
		@contextmenu.prevent
	>
		<div class="ctx-head" v-if="ctx.title">
			<button class="ctx-back" v-if="ctx.stack.length" @click="ctxGoBack">←</button>
			<span class="ctx-title">{{ ctx.title }}</span>
			<button class="ctx-close" @click="closeCtxMenu">×</button>
		</div>
		<div class="ctx-body">
			<template v-for="it in ctx.items" :key="it.key">
				<!-- 输入型项 -->
				<div v-if="it.input" class="ctx-item ctx-item--form">
					<span class="ctx-label">{{ it.label }}</span>
					<input
						class="ctx-input"
						:type="it.input.type || 'text'"
						v-model="ctx.inputs[it.key]"
						:placeholder="it.input.placeholder || ''"
						:min="it.input.min"
						:max="it.input.max"
						:step="it.input.step ?? (it.input.type === 'number' ? 1 : undefined)"
						@keydown.stop
						@keydown.enter.prevent="onCtxConfirm(it)"
					/>
					<button class="ctx-btn" @click="onCtxConfirm(it)">确定</button>
				</div>

				<!-- 普通项 -->
				<button v-else class="ctx-item" @click="onCtxItem(it)">
					<span class="ctx-label">{{ it.label }}</span>
					<span class="ctx-hint" v-if="it.hint">{{ it.hint }}</span>
					<span class="ctx-check" v-if="it.active">✓</span>
					<span class="ctx-arrow" v-if="it.children?.length">▸</span>
				</button>
			</template>
		</div>
	</div>
</template>

<script setup lang="ts">
/**
 * 右键菜单通用渲染器
 * - UI / 展开折叠 / 输入确认都在这里
 * - 外部三维视图只负责：拿菜单结构 + 调用 openAt(x, y, menu)
 */
import { reactive, ref, onMounted, onBeforeUnmount } from 'vue';
import { loading } from '@/utils/composition';

export type MenuItem = {
	key: string;
	label: string;
	children?: MenuItem[];
	action?: { op: string; params?: any };
	active?: boolean;
	hint?: string;
	input?: {
		type?: 'text' | 'number';
		min?: number;
		max?: number;
		step?: number;
		placeholder?: string;
		default?: string | number;
	};
};

export type MenuExec = (action: { op: string; params?: any }) => Promise<void>;

const props = defineProps<{
	/** threeDui 的 rootRef（用于防止菜单溢出到容器外） */
	rootRef?: any;
}>();

const menuRef = ref<HTMLElement | null>(null);

const ctx = reactive({
	open: false,
	left: 0,
	top: 0,
	title: '',
	items: [] as MenuItem[],
	stack: [] as MenuItem[][],
	titleStack: [] as string[],
	exec: null as null | MenuExec,
	inputs: {} as Record<string, string | number>,
});

function getRootEl(): HTMLElement | null {
	const r = props.rootRef as any;
	const el = r?.value ?? r;
	return el || null;
}

function initCtxInputs(items: MenuItem[]) {
	const map: Record<string, string | number> = {};
	items.forEach(it => {
		if (it.input) {
			map[it.key] = it.input.default ?? '';
		}
	});
	ctx.inputs = map;
}

function clampCtxPos(x: number, y: number) {
	const root = getRootEl();
	if (!root) {
		return { x, y };
	}
	const r = root.getBoundingClientRect();
	const pad = 8;
	const W = 260;
	const H = 300;
	return {
		x: Math.min(Math.max(pad, x), r.width - W - pad),
		y: Math.min(Math.max(pad, y), r.height - H - pad),
	};
}

function withLoading<T extends any[], R>(fn: (...args: T) => Promise<R>) {
	return async (...args: T) => {
		loading?.(true);
		try {
			return await fn(...args);
		} finally {
			loading?.(false);
		}
	};
}

/**
 * 对外：在 (x, y) 位置打开菜单
 * x,y 为相对 rootRef 左上角的坐标（threeDui 里已经算好了）
 */
function openAt(
	x: number,
	y: number,
	payload: { title: string; items: MenuItem[]; exec: MenuExec },
) {
	const { x: nx, y: ny } = clampCtxPos(x, y);
	ctx.left = nx;
	ctx.top = ny;
	ctx.title = payload.title;
	ctx.items = payload.items;
	ctx.stack = [];
	ctx.titleStack = [];
	ctx.exec = withLoading(payload.exec);
	initCtxInputs(payload.items);
	ctx.open = true;
}

function closeCtxMenu() {
	ctx.open = false;
	ctx.items = [];
	ctx.stack = [];
	ctx.titleStack = [];
	ctx.title = '';
	ctx.exec = null;
	ctx.inputs = {};
}

function ctxGoBack() {
	if (!ctx.stack.length) {
		return;
	}
	ctx.items = ctx.stack.pop()!;
	ctx.title = ctx.titleStack.pop() || ctx.title;
	initCtxInputs(ctx.items);
}

async function onCtxItem(it: MenuItem) {
	if (it.children?.length) {
		// 进入二级/多级
		ctx.stack.push(ctx.items);
		ctx.titleStack.push(ctx.title);
		ctx.items = it.children;
		ctx.title = it.label;
		initCtxInputs(ctx.items);
		return;
	}
	if (it.action && ctx.exec) {
		try {
			await ctx.exec(it.action);
		} finally {
			closeCtxMenu();
		}
	}
}

/** 提交输入型菜单项 */
async function onCtxConfirm(it: MenuItem) {
	if (!it.input || !it.action || !ctx.exec) {
		return;
	}
	let val: any = ctx.inputs[it.key];
	if (it.input.type === 'number') {
		const num = Number(val);
		if (Number.isNaN(num)) {
			return;
		}
		const min = it.input.min ?? -Infinity;
		const max = it.input.max ?? Infinity;
		val = Math.max(min, Math.min(max, num));
	}
	const params = { ...(it.action.params || {}) };
	Object.keys(params).forEach(k => {
		if (params[k] === '__INPUT__') {
			params[k] = val;
		}
	});
	try {
		await ctx.exec({ op: it.action.op, params });
	} finally {
		closeCtxMenu();
	}
}

/* ===== 点击页面空白关闭菜单（组件内部自己监听） ===== */
function onDocMousedown(ev: MouseEvent) {
	if (!ctx.open) {
		return;
	}
	const root = menuRef.value;
	if (!root) {
		return;
	}
	const el = ev.target as HTMLElement;
	if (!root.contains(el)) {
		closeCtxMenu();
	}
}

onMounted(() => {
	document.addEventListener('mousedown', onDocMousedown);
});
onBeforeUnmount(() => {
	document.removeEventListener('mousedown', onDocMousedown);
});

defineExpose({
	openAt,
	close: closeCtxMenu,
});
</script>

<style scoped>
/* ========== 右键菜单（通用渲染器） ========== */
.ctxmenu {
	position: absolute;
	z-index: 103;
	min-width: 220px;
	max-width: 280px;
	max-height: 60vh;
	overflow: auto;
	background: #fff;
	color: #262626;
	border: 1px solid #e5e5e5;
	border-radius: 10px;
	box-shadow: 0 10px 26px rgba(0, 0, 0, 0.18);
}

.ctx-head {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 8px 10px;
	font-weight: 700;
	border-bottom: 1px solid #f0f0f0;
}

.ctx-back {
	border: 1px solid #e5e5e5;
	background: #fff;
	border-radius: 6px;
	padding: 2px 8px;
	cursor: pointer;
}

.ctx-title {
	flex: 1;
}

.ctx-close {
	margin-left: auto;
	background: transparent;
	border: none;
	font-size: 18px;
	cursor: pointer;
}

.ctx-body {
	padding: 6px;
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.ctx-item {
	text-align: left;
	border: 1px solid #e5e5e5;
	background: #fff;
	border-radius: 6px;
	padding: 8px 10px;
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: space-between;
}

.ctx-item:hover {
	background: #e6f4ff;
	border-color: #91caff;
}

.ctx-arrow {
	opacity: 0.6;
}

/* 右键菜单表单项样式 */
.ctx-item--form {
	cursor: default;
	gap: 8px;
}

.ctx-item--form:hover {
	background: #fff;
	/* 表单不需要 hover 高亮 */
	border-color: #e5e5e5;
}

.ctx-input {
	flex: 1;
	min-width: 80px;
	border: 1px solid #e5e5e5;
	border-radius: 6px;
	padding: 6px 8px;
	outline: none;
}

.ctx-btn {
	margin-left: 8px;
	border: 1px solid #1677ff;
	background: #1677ff;
	color: #fff;
	border-radius: 6px;
	padding: 6px 12px;
	cursor: pointer;
}

.ctx-btn:hover {
	filter: brightness(0.95);
}
</style>
