<template>
	<!-- 纯数据子组件：只负责从后端拉 DIMM/CPU/PSU/FAN/DISK 列表 -->
	<DimmHover ref="dimmRef" @dimm-slots="onDimmSlots" />
	<CpuHover ref="cpuRef" @cpu-list="onCpuList" />
	<PowerHover ref="powerRef" @psu-list="onPsuList" />
	<FanHover ref="fanRef" @fan-list="onFanList" />
	<DiskHover ref="diskRef" @disk-list="onDiskList" />
</template>

<script setup lang="ts">
import { ref, type Ref } from 'vue';
import DimmHover from './parts/DimmHover.vue';
import CpuHover from './parts/CpuHover.vue';
import PowerHover from './parts/PowerHover.vue';
import FanHover from './parts/FanHover.vue';
import DiskHover from './parts/DiskHover.vue';
import { createApplyParts } from './composables/tools';

/* ============================================================================
 * Props：从 threeDui 传进来的“外部状态”
 * ========================================================================== */
interface Props {
	categoryMap: Record<string, string[]>;
	partMap: Record<string, any>;
	infoMap: Record<string, boolean>;
	sidebarAllow: Record<string, boolean>;
	getServerParts: () => any;
	rebuildList: () => void;
}

const props = defineProps<Props>();

/* ============================================================================
 * 子组件 ref
 * ========================================================================== */
const dimmRef = ref<any>(null);
const cpuRef = ref<any>(null);
const powerRef = ref<any>(null);
const fanRef = ref<any>(null);
const diskRef = ref<any>(null);

/* ============================================================================
 * 工具 & 类型定义
 * ========================================================================== */
const norm = (s: string) => String(s || '').trim().toLowerCase();

function allListNames(map: Record<string, string[]>) {
	const out: string[] = [];
	Object.values(map).forEach(arr => (arr || []).forEach(n => out.push(n)));
	return out;
}

/** Hover 子组件 ref key */
type PartRefKey = 'dimmRef' | 'cpuRef' | 'powerRef' | 'fanRef' | 'diskRef';

/** 每个业务分类的集中配置 */
type CategoryConfig = {
	alarmKeywords?: string[];
	modelFilter?: (m: { name: string }) => boolean;
	nameTest?: (name: string) => boolean;
	refKey?: PartRefKey;
	hasContextMenu?: boolean;
	pickerUseBackendNames?: boolean;
	markInfoOnLoad?: boolean;
};

/** 统一集中在一个 map，后面扩展只改这里即可 */
const CATEGORY_CONFIG: Record<string, CategoryConfig> = {
	Dimm: {
		alarmKeywords: ['memory'],
		modelFilter: m => /^dimm/i.test(m.name),
		nameTest: n => /^dimm/i.test(n),
		refKey: 'dimmRef',
		markInfoOnLoad: true,
	},
	Fan: {
		modelFilter: m => /^fan/i.test(m.name),
		nameTest: n => /^fan/i.test(n),
		refKey: 'fanRef',
		hasContextMenu: true,
		pickerUseBackendNames: true,
		markInfoOnLoad: true,
	},
	Psu: {
		alarmKeywords: ['psu'],
		nameTest: n => /^(?<psuOrPower>power|psu)/i.test(n),
		refKey: 'powerRef',
		hasContextMenu: true,
		pickerUseBackendNames: true,
		markInfoOnLoad: true,
	},
	Disk: {
		modelFilter: m => /^disk/i.test(m.name),
		nameTest: n => /^(?<diskName>Disk|disk)/.test(n),
		refKey: 'diskRef',
		hasContextMenu: true,
		pickerUseBackendNames: true,
	},
	Cover: {
		nameTest: n => /^Cover/.test(n),
	},
	Chassis: {
		nameTest: n => /^chassis/i.test(n),
	},
	Mainboard: {
		nameTest: n => /^mainboard/i.test(n),
	},
	CPU: {
		nameTest: n => /^(?<cpuName>cpu)/i.test(n),
		refKey: 'cpuRef',
		markInfoOnLoad: true,
	},
};

/** 分类优先顺序（原 if-else 的顺序） */
const CATEGORY_ORDER = [
	'Dimm',
	'Fan',
	'Psu',
	'Radiator',
	'Disk',
	'Cover',
	'I/O Modules',
	'Chassis',
	'Mainboard',
	'CPU',
] as const;

/** 默认的分类逻辑：名字 → 分类 */
function baseGetCategory(rawName: string): string {
	const n = String(rawName);
	for (const cat of CATEGORY_ORDER) {
		const cfg = CATEGORY_CONFIG[cat];
		if (cfg?.nameTest && cfg.nameTest(n)) {
			return cat;
		}
	}
	// 兜底：还是归到 Chassis
	return 'Chassis';
}

/** 把 “某个 Hover 子组件” 的取数逻辑收成一个小工具，避免重复代码 */
async function buildInfoFromRef(hoverRef: Ref<any> | undefined, partName: string) {
	if (!hoverRef?.value) {
		return { pairs: [] as { k: string; v: string }[], text: partName, has: false };
	}
	const pairs = (await hoverRef.value.buildPairs(partName)) || [];
	const text = hoverRef.value.getBasicText(partName) || partName;
	const has = pairs.length > 0;
	return { pairs, text, has };
}

/** 哪些分类在模型加载完成时默认标记为 infoMap[name] = true */
function getCategoriesToMarkInfoOnLoad(): string[] {
	return Object.entries(CATEGORY_CONFIG)
		.filter(([, cfg]) => cfg.markInfoOnLoad)
		.map(([cat]) => cat);
}

/** 各类部件的 three.js 模型筛选规则（给 createApplyParts 用） */
function getModelFilter(category: string) {
	const cfg = CATEGORY_CONFIG[category];
	if (cfg?.modelFilter) {
		return cfg.modelFilter;
	}
	// 默认返回 “全部不匹配”，避免误选
	return (_m: { name: string }) => false;
}

/* ============================================================================
 * usePartBusiness：悬浮信息 / 告警映射 / 右键菜单 / picker
 * ========================================================================== */
interface UsePartBusinessDeps {
	getCategory?: (partName: string) => string | undefined;
	categoryMap: Record<string, string[]>;
	dimmRef: Ref<any>;
	cpuRef: Ref<any>;
	powerRef: Ref<any>;
	fanRef: Ref<any>;
	diskRef: Ref<any>;
}

function usePartBusiness(deps: UsePartBusinessDeps) {
	const {
		getCategory: externalGetCategory,
		categoryMap: bizCategoryMap,
		dimmRef: dimmRefDep,
		cpuRef: cpuRefDep,
		powerRef: powerRefDep,
		fanRef: fanRefDep,
		diskRef: diskRefDep,
	} = deps;

	/** 统一收集所有 ref，方便通过 refKey 访问 */
	const refMap: Record<PartRefKey, Ref<any>> = {
		dimmRef: dimmRefDep,
		cpuRef: cpuRefDep,
		powerRef: powerRefDep,
		fanRef: fanRefDep,
		diskRef: diskRefDep,
	};

	// 内部一律用这个获取分类；如果外部传了 getCategory，就优先用外面的
	const localGetCategory = (partName: string) =>
		externalGetCategory?.(partName) || baseGetCategory(partName);

	/** 通过分类配置找到对应的 ref */
	const getRefByCategory = (cat: string): Ref<any> | undefined => {
		const cfg = CATEGORY_CONFIG[cat];
		if (!cfg?.refKey) {
			return undefined;
		}
		return refMap[cfg.refKey];
	};

	/** 部件 hover/点击：根据 name 拿 KV / 文本 */
	async function fetchInfoFor(partName: string) {
		const cat = localGetCategory(partName);
		const hoverRef = getRefByCategory(cat);

		if (hoverRef) {
			return buildInfoFromRef(hoverRef, partName);
		}

		// 未命中任何业务规则时，返回兜底信息
		return { pairs: [] as { k: string; v: string }[], text: partName, has: false };
	}

	/** 告警 subject -> 需要高亮的部件名列表 */
	function findListNamesBySubject(subject: string) {
		const s = norm(subject);
		if (!s) {
			return [];
		}

		// 1) 先看 Subject 是否命中某个分类的 alarmKeywords
		const matchedCats = Object.entries(CATEGORY_CONFIG)
			.filter(([, cfg]) =>
				cfg.alarmKeywords?.some(kw => s.includes(kw.toLowerCase())),
			)
			.map(([cat]) => cat);

		if (matchedCats.length) {
			const result: string[] = [];
			matchedCats.forEach(cat => {
				(bizCategoryMap[cat] || []).forEach(n => result.push(n));
			});
			return result;
		}

		// 2) 没有命中关键词时，回退到名字包含匹配（原来的行为）
		return allListNames(bizCategoryMap).filter(n =>
			n.toLowerCase().includes(s),
		);
	}

	/** 右键菜单 */
	async function getContextMenuFor(
		partName: string,
	): Promise<
		| {
				title: string;
				items: any[];
				exec: (a: { op: string; params?: any }) => Promise<void>;
		  }
		| null
	> {
		const cat = localGetCategory(partName);
		const cfg = CATEGORY_CONFIG[cat];
		if (!cfg?.hasContextMenu) {
			return null;
		}

		const instRef = getRefByCategory(cat);
		const inst = instRef?.value;
		if (!inst?.getContextMenu || !inst?.execContextAction) {
			return null;
		}

		const menu = await inst.getContextMenu(partName);
		const exec = async (a: { op: string; params?: any }) => {
			await inst.execContextAction(a);
		};
		return { ...menu, exec };
	}

	/**
	 * 选择单（picker）使用的「同类可选部件」：
	 * - Fan/Disk：优先使用后端 getNames()（如果组件提供）
	 * - 只保留拥有 2 个以上 KV 的部件
	 */
	async function getPickerItemsFor(category: string) {
		const cfg = CATEGORY_CONFIG[category];
		const providerRef = cfg?.refKey ? refMap[cfg.refKey] : undefined;
		const inst = providerRef?.value;

		let names = (bizCategoryMap[category] || []).slice();

		if (cfg?.pickerUseBackendNames && inst?.getNames) {
			names = inst.getNames();
		}

		const items: string[] = [];
		for (const n of names) {
			const detail = await fetchInfoFor(n);
			if (detail.pairs && detail.pairs.length > 1) {
				items.push(n);
			}
		}

		return items;
	}

	return {
		getCategory: localGetCategory,
		fetchInfoFor,
		findListNamesBySubject,
		getContextMenuFor,
		getPickerItemsFor,
	};
}

/* ============================================================================
 * 通用部件同步逻辑
 * ========================================================================== */
interface PartSyncHandlerOptions {
	setNames: (names: string[]) => void;
	apply: () => void;
	rebuildList: () => void;
	markInfo?: (name: string) => void;
	markSidebar?: (name: string) => void;
}

/** 创建一个通用「部件列表同步」处理函数 */
function createPartSyncHandler(opts: PartSyncHandlerOptions) {
	const { setNames, apply, rebuildList, markInfo, markSidebar } = opts;

	return (names: string[] = []) => {
		setNames(names || []);
		apply();
		if (markInfo || markSidebar) {
			names.forEach(n => {
				markInfo?.(n);
				markSidebar?.(n);
			});
		}
		rebuildList();
	};
}

interface UsePartSyncDeps {
	categoryMap: Record<string, string[]>;
	partMap: Record<string, any>;
	infoMap: Record<string, boolean>;
	sidebarAllow: Record<string, boolean>;
	dimmRef: Ref<any>;
	cpuRef: Ref<any>;
	powerRef: Ref<any>;
	fanRef: Ref<any>;
	diskRef: Ref<any>;
	getServerParts: () => any;
	rebuildList: () => void;
}

/**
 * usePartSync：
 *  - 内部负责 createApplyParts + applyDimms/applyFans/applyDisks
 *  - 暴露 onDimmSlots/onFanList/onDiskList 等 handler
 *  - 暴露 applyAllOnModelLoaded：模型加载完成后统一调用一次
 */
function usePartSync(deps: UsePartSyncDeps) {
	const {
		categoryMap: syncCategoryMap,
		partMap,
		infoMap,
		sidebarAllow,
		dimmRef: dimmRefDep,
		cpuRef: cpuRefDep,
		powerRef: powerRefDep,
		fanRef: fanRefDep,
		diskRef: diskRefDep,
		getServerParts,
		rebuildList,
	} = deps;

	/** 按 category 统一创建 apply 函数 */
	function createApplyByCategory(category: string, getRawChildNames: () => string[]) {
		return createApplyParts({
			category,
			getServerParts,
			modelFilter: getModelFilter(category),
			getChildNames: () => getRawChildNames(),
			partMap,
			categoryMap: syncCategoryMap,
			infoMap,
			sidebarAllow,
		});
	}

	let dimmSlotsFromChild: string[] = [];
	let fanNamesFromChild: string[] = [];
	let diskNamesFromChild: string[] = [];

	const applyDimms = createApplyByCategory('Dimm', () => dimmSlotsFromChild);
	const applyFans = createApplyByCategory('Fan', () => fanNamesFromChild || []);
	const applyDisks = createApplyByCategory('Disk', () => diskNamesFromChild);

	const onDimmSlots = createPartSyncHandler({
		setNames: slots => {
			dimmSlotsFromChild = slots || [];
		},
		apply: applyDimms,
		rebuildList,
	});

	const onCpuList = (_: string[]) => {};
	const onPsuList = (_: string[]) => {};

	const onFanList = createPartSyncHandler({
		setNames: names => {
			fanNamesFromChild = names || [];
		},
		apply: applyFans,
		rebuildList,
		markInfo: n => {
			infoMap[n] = true;
		},
		markSidebar: n => {
			sidebarAllow[n] = true;
		},
	});

	const onDiskList = createPartSyncHandler({
		setNames: names => {
			diskNamesFromChild = names || [];
		},
		apply: applyDisks,
		rebuildList,
		markInfo: n => {
			infoMap[n] = true;
		},
		markSidebar: n => {
			sidebarAllow[n] = true;
		},
	});

	/** 模型加载完成时的一次性初始化逻辑 */
	async function applyAllOnModelLoaded() {
		// 1) 应用各类部件
		applyDimms();
		applyDisks();
		applyFans();
		rebuildList();

		// 2) 根据配置，把某些分类统一标记为有 info
		getCategoriesToMarkInfoOnLoad().forEach(cat => {
			(syncCategoryMap[cat] || []).forEach(n => {
				infoMap[n] = true;
			});
		});

		// 3) 刷新各个 Hover 组件
		await Promise.allSettled([
			fanRefDep.value?.refresh?.(),
			powerRefDep.value?.refresh?.(),
			cpuRefDep.value?.refresh?.(),
			dimmRefDep.value?.refresh?.(),
			diskRefDep.value?.refresh?.(),
		]);
	}

	return {
		onDimmSlots,
		onCpuList,
		onPsuList,
		onFanList,
		onDiskList,
		applyAllOnModelLoaded,
	};
}

/* ============================================================================
 * 实例化业务逻辑 + 暴露给父组件
 * ========================================================================== */
const {
	getCategory,
	fetchInfoFor,
	findListNamesBySubject,
	getContextMenuFor,
	getPickerItemsFor,
} = usePartBusiness({
	categoryMap: props.categoryMap,
	dimmRef,
	cpuRef,
	powerRef,
	fanRef,
	diskRef,
});

const {
	onDimmSlots,
	onCpuList,
	onPsuList,
	onFanList,
	onDiskList,
	applyAllOnModelLoaded,
} = usePartSync({
	categoryMap: props.categoryMap,
	partMap: props.partMap,
	infoMap: props.infoMap,
	sidebarAllow: props.sidebarAllow,
	dimmRef,
	cpuRef,
	powerRef,
	fanRef,
	diskRef,
	getServerParts: props.getServerParts,
	rebuildList: props.rebuildList,
});

/**
 * 对外暴露给 threeDui：
 * - getCategory / fetchInfoFor / findListNamesBySubject
 * - getContextMenuFor / getPickerItemsFor
 * - applyAllOnModelLoaded：在 threeDui 的 onModelLoaded 里调用一次
 */
defineExpose({
	getCategory,
	fetchInfoFor,
	findListNamesBySubject,
	getContextMenuFor,
	getPickerItemsFor,
	applyAllOnModelLoaded,
});
</script>
