<template>
	<div class="relation-graph-wrapper">
		<div class="graph-controls">
			<div
				v-for="item in controlBtns"
				:key="item.type"
				:class="['control-btn', { 'control-btn-active': currentLayoutType === item.type }]"
				@click="handleClickControlBtn(item.type)"
			>
				<img :src="item.icon" alt="" />
			</div>
		</div>
		<div ref="containerRef" class="graph-container"></div>

		<div v-if="selectedNode" class="node-popup">
			<div class="popup-header">
				<img :src="selectedNode.image || defaultIcon" alt="" class="popup-icon" />
				<div class="popup-title">{{ selectedNode.name }}</div>
				<el-icon class="close-icon" @click="selectedNode = null">
					<Close />
				</el-icon>
			</div>
			<div class="popup-body">
				<div v-if="selectedNode.isSanctioned" class="tag-row">
					<span class="red-dot"></span>
					<span class="red-text">被制裁实体</span>
				</div>
				<div v-if="selectedNode.description && selectedNode.description.length > 0" class="desc-row">
					<p class="desc" v-for="item in selectedNode.description" :key="item">{{ item }}</p>
				</div>
				<p v-else class="desc">暂无描述</p>
			</div>
		</div>
	</div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from "vue";
import G6 from "@antv/g6";
import { Close } from "@element-plus/icons-vue";
import echartsIcon01 from "../assets/echartsicon01.png";
import echartsIcon02 from "../assets/echartsicon02.png";
import echartsIcon03 from "../assets/echartsicon03.png";
import defaultIcon from "../assets/echartsicon03.png";

import { getSingleSanctionEntityInfo } from "@/api/exportControlV2.0";

const props = defineProps({
	graphData: {
		type: Object,
		default: () => ({ nodes: [], links: [] })
	},
	treeData: {
		type: Object,
		default: () => null
	},
	controlActive: {
		type: Number,
		default: 1
	}
});

const emit = defineEmits(["nodeClick", "layoutChange"]);

const containerRef = ref(null);
const graphInstance = ref(null);
const currentLayoutType = ref(1);
const selectedNode = ref(null);

const controlBtns = [
	{ type: 1, icon: echartsIcon01, name: "力导向布局" },
	{ type: 2, icon: echartsIcon02, name: "树布局" },
	{ type: 3, icon: echartsIcon03, name: "环状布局" }
];

const initGraph = (layoutType = 1) => {
	if (!containerRef.value) return;

	destroyGraph();

	nextTick(() => {
		const container = containerRef.value;
		const width = container.offsetWidth || 800;
		const height = container.offsetHeight || 600;

		if (layoutType === 2) {
			initTreeGraph(width, height);
		} else if (layoutType === 3) {
			initCircularGraph(width, height);
		} else {
			initNormalGraph(layoutType, width, height);
		}
	});
};

const initNormalGraph = (layoutType, width, height) => {
	const data = processGraphData(props.graphData);

	if (!data.nodes || data.nodes.length === 0) return;

	const layout = {
		type: "force",
		center: [width / 2, height / 2],
		preventOverlap: true,
		nodeSpacing: 80,
		linkDistance: 250,
		nodeStrength: -800,
		edgeStrength: 0.1,
		collideStrength: 0.8,
		alphaDecay: 0.01,
		alphaMin: 0.001
	};

	graphInstance.value = new G6.Graph({
		container: containerRef.value,
		width,
		height,
		fitView: true,
		fitViewPadding: 100,
		fitCenter: true,
		animate: true,
		animateCfg: {
			duration: 300,
			easing: "easeLinear"
		},
		minZoom: 0.1,
		maxZoom: 10,
		modes: {
			default: [
				"drag-canvas",
				"zoom-canvas",
				"drag-node",
				{
					type: "activate-relations",
					trigger: "mouseenter",
					resetSelected: true
				}
			]
		},
		layout,
		defaultNode: {
			type: "image",
			size: 40,
			clipCfg: {
				show: true,
				type: "circle",
				r: 20
			},
			labelCfg: {
				position: "bottom",
				offset: 10,
				style: {
					fill: "#333",
					fontSize: 11,
					fontFamily: "Microsoft YaHei",
					textAlign: "center",
					background: {
						fill: "rgba(255, 255, 255, 0.95)",
						padding: [4, 6, 4, 6],
						radius: 4
					}
				}
			}
		},
		defaultEdge: {
			type: "quadratic",
			style: {
				stroke: "#5B8FF9",
				lineWidth: 3,
				opacity: 0.9,
				// 添加边的边框效果
				strokeOpacity: 1,
				endArrow: {
					path: "M 0,0 L 12,6 L 12,-6 Z",
					fill: "#5B8FF9"
				}
			},
			labelCfg: {
				autoRotate: true,
				style: {
					fill: "#333",
					fontSize: 10,
					fontFamily: "Microsoft YaHei",
					background: {
						fill: "#fff",
						padding: [2, 4, 2, 4],
						radius: 2
					}
				}
			}
		},
		nodeStateStyles: {
			active: {
				shadowColor: "#1459BB",
				shadowBlur: 15,
				stroke: "#1459BB",
				lineWidth: 3
			},
			inactive: {
				opacity: 0.3
			}
		},
		edgeStateStyles: {
			active: {
				stroke: "#1459BB",
				lineWidth: 4
			},
			inactive: {
				opacity: 0.15
			}
		}
	});

	graphInstance.value.data(data);
	graphInstance.value.render();
	bindGraphEvents();
};

const initCircularGraph = (width, height) => {
	const data = processGraphData(props.graphData);

	if (!data.nodes || data.nodes.length === 0) return;

	const centerX = width / 2;
	const centerY = height / 2;
	const radius = Math.min(width, height) / 2 - 120;

	const otherNodes = data.nodes.filter(n => !n.isCenter);
	const nodeCount = otherNodes.length;

	otherNodes.forEach((node, index) => {
		const angle = (2 * Math.PI * index) / nodeCount - Math.PI / 2;
		node.x = centerX + radius * Math.cos(angle);
		node.y = centerY + radius * Math.sin(angle);
	});

	const centerNode = data.nodes.find(n => n.isCenter);
	if (centerNode) {
		centerNode.x = centerX;
		centerNode.y = centerY;
		centerNode.fx = centerX;
		centerNode.fy = centerY;
	}

	graphInstance.value = new G6.Graph({
		container: containerRef.value,
		width,
		height,
		fitView: false,
		fitCenter: false,
		animate: true,
		animateCfg: {
			duration: 300,
			easing: "easeLinear"
		},
		minZoom: 0.1,
		maxZoom: 10,
		modes: {
			default: [
				"drag-canvas",
				"zoom-canvas",
				"drag-node",
				{
					type: "activate-relations",
					trigger: "mouseenter",
					resetSelected: true
				}
			]
		},
		defaultNode: {
			type: "image",
			size: 40,
			clipCfg: {
				show: true,
				type: "circle",
				r: 20
			},
			labelCfg: {
				position: "bottom",
				offset: 10,
				style: {
					fill: "#333",
					fontSize: 11,
					fontFamily: "Microsoft YaHei",
					textAlign: "center",
					background: {
						fill: "rgba(255, 255, 255, 0.95)",
						padding: [4, 6, 4, 6],
						radius: 4
					}
				}
			}
		},
		defaultEdge: {
			type: "quadratic",
			style: {
				stroke: "#5B8FF9",
				lineWidth: 3,
				opacity: 0.9,
				endArrow: {
					path: "M 0,0 L 12,6 L 12,-6 Z",
					fill: "#5B8FF9"
				}
			},
			labelCfg: {
				autoRotate: true,
				style: {
					fill: "#333",
					fontSize: 10,
					fontFamily: "Microsoft YaHei",
					background: {
						fill: "#fff",
						padding: [2, 4, 2, 4],
						radius: 2
					}
				}
			}
		},
		nodeStateStyles: {
			active: {
				shadowColor: "#1459BB",
				shadowBlur: 15,
				stroke: "#1459BB",
				lineWidth: 3
			},
			inactive: {
				opacity: 0.3
			}
		},
		edgeStateStyles: {
			active: {
				stroke: "#1459BB",
				lineWidth: 4
			},
			inactive: {
				opacity: 0.15
			}
		}
	});

	graphInstance.value.data(data);
	graphInstance.value.render();
	bindGraphEvents();
};

const initTreeGraph = (width, height) => {
	const treeDataSource = convertGraphToTree(props.graphData);

	if (!treeDataSource) return;

	graphInstance.value = new G6.TreeGraph({
		container: containerRef.value,
		width,
		height,
		fitView: true,
		fitViewPadding: 80,
		animate: true,
		animateCfg: {
			duration: 300,
			easing: "easeLinear"
		},
		minZoom: 0.1,
		maxZoom: 10,
		modes: {
			default: [
				"drag-canvas",
				"zoom-canvas",
				"drag-node",
				{
					type: "collapse-expand",
					onChange: function onChange(item, collapsed) {
						const data = item.getModel();
						data.collapsed = collapsed;
						return true;
					}
				}
			]
		},
		layout: {
			type: "compactBox",
			direction: "LR",
			getId: function getId(d) {
				return d.id;
			},
			getHeight: function getHeight() {
				return 16;
			},
			getWidth: function getWidth() {
				return 16;
			},
			getVGap: function getVGap() {
				return 30;
			},
			getHGap: function getHGap() {
				return 120;
			}
		},
		defaultNode: {
			type: "image",
			size: 40,
			clipCfg: {
				show: true,
				type: "circle",
				r: 20
			},
			labelCfg: {
				position: "right",
				offset: 10,
				style: {
					fill: "#333",
					fontSize: 11,
					fontFamily: "Microsoft YaHei",
					background: {
						fill: "rgba(255, 255, 255, 0.95)",
						padding: [4, 6, 4, 6],
						radius: 4
					}
				}
			}
		},
		defaultEdge: {
			type: "cubic-horizontal",
			style: {
				stroke: "#5B8FF9",
				lineWidth: 3
			}
		},
		nodeStateStyles: {
			active: {
				shadowColor: "#1459BB",
				shadowBlur: 15,
				stroke: "#1459BB",
				lineWidth: 3
			}
		}
	});

	graphInstance.value.data(treeDataSource);
	graphInstance.value.render();
	graphInstance.value.fitView();
	bindGraphEvents();
};

const convertGraphToTree = graphData => {
	if (!graphData || !graphData.nodes || graphData.nodes.length === 0) {
		return null;
	}

	const nodes = graphData.nodes;
	const links = graphData.links || graphData.edges || [];
	const centerNode = nodes[0];
	const centerId = String(centerNode.id || "0");

	const childIdSet = new Set();
	const childrenNodes = [];

	links.forEach(link => {
		const source = String(link.source);
		const target = String(link.target);

		if (source === centerId && !childIdSet.has(target)) {
			const node = nodes.find(n => String(n.id) === target);
			if (node) {
				childIdSet.add(target);
				childrenNodes.push({
					id: target,
					label: node.name || "",
					img: node.image || defaultIcon,
					size: node.symbolSize || 40,
					name: node.name,
					image: node.image,
					isSanctioned: node.isSanctioned
				});
			}
		} else if (target === centerId && !childIdSet.has(source)) {
			const node = nodes.find(n => String(n.id) === source);
			if (node) {
				childIdSet.add(source);
				childrenNodes.push({
					id: source,
					label: node.name || "",
					img: node.image || defaultIcon,
					size: node.symbolSize || 40,
					name: node.name,
					image: node.image,
					isSanctioned: node.isSanctioned
				});
			}
		}
	});

	if (childrenNodes.length === 0) {
		nodes.slice(1).forEach(node => {
			const nodeId = String(node.id);
			if (!childIdSet.has(nodeId)) {
				childIdSet.add(nodeId);
				childrenNodes.push({
					id: nodeId,
					label: node.name || "",
					img: node.image || defaultIcon,
					size: node.symbolSize || 40,
					name: node.name,
					image: node.image,
					isSanctioned: node.isSanctioned
				});
			}
		});
	}

	return {
		id: centerId,
		label: centerNode.name || "",
		img: centerNode.image || defaultIcon,
		size: centerNode.symbolSize || 60,
		name: centerNode.name,
		image: centerNode.image,
		isSanctioned: centerNode.isSanctioned,
		children: childrenNodes
	};
};

const processGraphData = rawData => {
	if (!rawData || !rawData.nodes || rawData.nodes.length === 0) {
		return { nodes: [], edges: [] };
	}

	const nodeMap = new Map();
	const nodes = [];

	rawData.nodes.forEach((node, index) => {
		const nodeId = String(node.id || index);

		if (nodeMap.has(nodeId)) {
			return;
		}
		nodeMap.set(nodeId, true);

		const isCenter = index === 0;
		const size = node.symbolSize || (isCenter ? 60 : 40);

		nodes.push({
			id: nodeId,
			label: node.name || "",
			img: node.image || defaultIcon,
			size,
			isCenter,
			clipCfg: {
				show: true,
				type: "circle",
				r: size / 2
			},
			style: {
				cursor: "pointer"
			},
			labelCfg: {
				position: "bottom",
				offset: 12,
				style: {
					fill: isCenter ? "#1459BB" : "#333",
					fontSize: isCenter ? 13 : 11,
					fontWeight: isCenter ? "bold" : "normal",
					fontFamily: "Microsoft YaHei",
					textAlign: "center"
				}
			},
			...node,
			id: nodeId
		});
	});

	const edgeMap = new Map();
	const edges = [];

	const rawEdges = rawData.links || rawData.edges || [];
	rawEdges.forEach((edge, index) => {
		const source = String(edge.source);
		const target = String(edge.target);
		const edgeKey = `${source}-${target}`;

		if (edgeMap.has(edgeKey)) {
			return;
		}
		if (!nodeMap.has(source) || !nodeMap.has(target)) {
			return;
		}
		edgeMap.set(edgeKey, true);

		edges.push({
			id: `edge-${index}`,
			source,
			target,
			label: edge.name || ""
		});
	});

	return { nodes, edges };
};

const bindGraphEvents = () => {
	if (!graphInstance.value) return;

	graphInstance.value.on("node:click", evt => {
		const node = evt.item;
		const model = node.getModel();
		console.log("点击节点model", model);
		const id = model.id.split("-")[1];
		let desList = [];
		getSingleSanctionEntityInfo(id).then(res => {
			console.log("制裁实体信息", res);
			if (res.data.sanInfoList && res.data.sanInfoList.length > 0) {
				desList = res.data.sanInfoList.map(item => item.sanReason);
			}
		});
		selectedNode.value = { ...model, description: desList };
		emit("nodeClick", model);
	});

	graphInstance.value.on("canvas:click", () => {
		selectedNode.value = null;
	});
};

const handleClickControlBtn = btn => {
	currentLayoutType.value = btn;
	emit("layoutChange", btn);
	initGraph(btn);
};

const destroyGraph = () => {
	if (graphInstance.value) {
		graphInstance.value.destroy();
		graphInstance.value = null;
	}
};

const handleResize = () => {
	if (graphInstance.value && containerRef.value) {
		const width = containerRef.value.offsetWidth;
		const height = containerRef.value.offsetHeight;
		graphInstance.value.changeSize(width, height);
		graphInstance.value.fitView();
	}
};

watch(
	() => props.graphData,
	() => {
		initGraph(currentLayoutType.value);
	},
	{ deep: true }
);

watch(
	() => props.treeData,
	() => {
		if (currentLayoutType.value === 2) {
			initGraph(2);
		}
	},
	{ deep: true }
);

watch(
	() => props.controlActive,
	newVal => {
		if (newVal !== currentLayoutType.value) {
			handleClickControlBtn(newVal);
		}
	}
);

onMounted(() => {
	initGraph(1);
	window.addEventListener("resize", handleResize);
});

onUnmounted(() => {
	window.removeEventListener("resize", handleResize);
	destroyGraph();
});

defineExpose({
	refresh: () => initGraph(currentLayoutType.value),
	changeLayout: type => handleClickControlBtn(type),
	getGraph: () => graphInstance.value
});
</script>

<style lang="scss" scoped>
.relation-graph-wrapper {
	position: relative;
	width: 100%;
	height: 100%;
}

.graph-container {
	width: 100%;
	height: 100%;
}

.graph-controls {
	position: absolute;
	top: 16px;
	right: 16px;
	display: flex;
	gap: 8px;
	z-index: 10;

	.control-btn {
		width: 32px;
		height: 32px;
		border-radius: 4px;
		border: 1px solid rgba(234, 236, 238, 1);
		background: rgba(255, 255, 255, 1);
		display: flex;
		align-items: center;
		justify-content: center;
		cursor: pointer;

		img {
			width: 16px;
			height: 16px;
		}

		&:hover {
			border-color: rgba(5, 95, 194, 0.5);
		}
	}

	.control-btn-active {
		border-color: rgba(5, 95, 194, 1);
		background: rgba(231, 243, 255, 1);
	}
}

.node-popup {
	position: absolute;
	bottom: 16px;
	left: 16px;
	width: 320px;
	background: rgba(255, 255, 255, 1);
	border-radius: 8px;
	box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1);
	border: 1px solid rgba(234, 236, 238, 1);
	z-index: 20;

	.popup-header {
		display: flex;
		align-items: center;
		padding: 12px 16px;
		border-bottom: 1px solid rgba(234, 236, 238, 1);

		.popup-icon {
			width: 32px;
			height: 32px;
			margin-right: 8px;
			border-radius: 50%;
			object-fit: cover;
		}

		.popup-title {
			flex: 1;
			font-size: 16px;
			font-weight: 700;
			font-family: "Microsoft YaHei";
			color: rgba(59, 65, 75, 1);
			white-space: nowrap;
			overflow: hidden;
			text-overflow: ellipsis;
		}

		.close-icon {
			cursor: pointer;
			color: rgba(132, 136, 142, 1);
			font-size: 16px;

			&:hover {
				color: rgba(5, 95, 194, 1);
			}
		}
	}

	.popup-body {
		padding: 12px 16px;

		.tag-row {
			display: flex;
			align-items: center;
			margin-bottom: 8px;

			.red-dot {
				width: 6px;
				height: 6px;
				border-radius: 50%;
				background: rgba(245, 63, 63, 1);
				margin-right: 8px;
			}

			.red-text {
				font-size: 14px;
				font-family: "Microsoft YaHei";
				color: rgba(245, 63, 63, 1);
			}
		}

		.desc {
			font-size: 14px;
			font-family: "Microsoft YaHei";
			line-height: 22px;
			color: rgba(95, 101, 108, 1);
			margin: 0;
		}
	}
}
</style>
