<!--
  递归多级树节点组件
  - depth: 当前节点深度（0 起始）
  - activePath: 当前选中节点 id 链（用于高亮）
  - searchKey: 搜索关键词（命中时高亮 + 自动展开父链）
  - 事件:
    @toggle: 展开或收起
    @select: 选中节点（仅叶子节点触发；非叶子节点仅展开/收起）
-->
<template>
	<view class="tn">
		<view
			class="tn-row"
			:class="{
				'tn-row--active': isActive,
				'tn-row--match': isMatch,
				'tn-row--open': node._open
			}"
			:style="{ paddingLeft: indent + 'rpx' }"
			@tap="onTapRow"
		>
			<text v-if="hasChildren" class="tn-arrow" :class="{ 'tn-arrow--open': node._open }">›</text>
			<text v-else class="tn-leaf"></text>
			<text class="tn-txt" :class="{ 'tn-txt--hl': isMatch }">
				<rich-text :nodes="labelHtml"></rich-text>
			</text>
		</view>
		<view v-if="node._open && hasChildren" class="tn-children">
			<tree-node
				v-for="(child, idx) in node.children"
				:key="child.type + '-' + (child.CBid || child.HBid || child.id) + '-' + idx"
				:node="child"
				:depth="depth + 1"
				:sibling-index="idx"
				:active-path="activePath"
				:search-key="searchKey"
				@select="bubble('select', $event)"
			/>
		</view>
	</view>
</template>

<script>
/**
 * 递归树节点：自身即通过 name: 'treeNode' 注册，递归引用自己
 */
export default {
	name: 'treeNode',
	props: {
		node: { type: Object, required: true },
		depth: { type: Number, default: 0 },
		siblingIndex: { type: Number, default: 0 },
		activePath: { type: Array, default: () => [] },
		searchKey: { type: String, default: '' }
	},
	computed: {
		hasChildren() {
			return !!(this.node.children && this.node.children.length);
		},
		indent() {
			// 每级 28rpx 缩进
			return 16 + this.depth * 28;
		},
		nodeKey() {
			return this.node.type + '-' + (this.node.CBid || this.node.HBid || this.node.id) + '-' + this.siblingIndex;
		},
		isActive() {
			return this.activePath[this.activePath.length - 1] === this.nodeKey;
		},
		isMatch() {
			const kw = (this.searchKey || '').trim().toLowerCase();
			if (!kw) return false;
			return (this.node.descripts || '').toLowerCase().includes(kw);
		},
		labelHtml() {
			const kw = (this.searchKey || '').trim();
			const label = this.node.descripts || '';
			if (!kw) return this.escapeHtml(label);
			const lower = label.toLowerCase();
			const idx = lower.indexOf(kw.toLowerCase());
			if (idx < 0) return this.escapeHtml(label);
			const before = this.escapeHtml(label.slice(0, idx));
			const hit = this.escapeHtml(label.slice(idx, idx + kw.length));
			const after = this.escapeHtml(label.slice(idx + kw.length));
			return `${before}<span style="color:#1890ff;font-weight:600;">${hit}</span>${after}`;
		}
	},
	methods: {
		escapeHtml(s) {
			return String(s)
				.replace(/&/g, '&amp;')
				.replace(/</g, '&lt;')
				.replace(/>/g, '&gt;')
				.replace(/"/g, '&quot;');
		},
		onTapRow() {
			// 任意层级点击都选中（父节点在父组件内切换展开 + 加载右侧）
			this.$emit('select', this.node);
		},
		bubble(name, payload) {
			this.$emit(name, payload);
		}
	}
};
</script>

<style scoped>
.tn {
	display: block;
}
.tn-row {
	display: flex;
	align-items: center;
	padding: 18rpx 16rpx 18rpx 0;
	font-size: 26rpx;
	color: #1f2937;
	border-left: 6rpx solid transparent;
	position: relative;
	min-height: 56rpx;
	box-sizing: border-box;
}
.tn-row--active {
	background: #eef6ff;
	border-left-color: #1890ff;
	color: #1890ff;
	font-weight: 600;
}
.tn-row--match {
	background: #fffbe6;
}
.tn-row--active.tn-row--match {
	background: #fff7d9;
}
.tn-arrow {
	display: inline-block;
	width: 28rpx;
	font-size: 32rpx;
	color: #9ca3af;
	transition: transform 0.2s;
	transform: rotate(0deg);
	line-height: 1;
	margin-right: 6rpx;
}
.tn-arrow--open {
	transform: rotate(90deg);
	color: #1890ff;
}
.tn-leaf {
	display: inline-block;
	width: 28rpx;
	margin-right: 6rpx;
}
.tn-txt {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	/* white-space: nowrap; */
}
.tn-txt--hl {
	color: #1890ff;
}
.tn-count {
	font-size: 22rpx;
	color: #1890ff;
	background: #eef6ff;
	border-radius: 20rpx;
	padding: 2rpx 14rpx;
	margin-left: 10rpx;
	white-space: nowrap;
}
.tn-children {
	background: #f5f7fa;
}
</style>