<template>
	<div
		class="ai-mention"
		:class="{
			'is-border': border
		}"
	>
		<div class="ai-mention__editor" :ref="setRefs('editor')" :style="{ height }"></div>

		<el-popover
			:ref="setRefs('popover')"
			trigger="click"
			placement="bottom-start"
			:popper-class="['ai-mention__popper', popperClass || '']"
			:popper-style="{
				padding: '0',
				borderRadius: '6px'
			}"
			:width="200"
			:offset="5"
			:hide-after="0"
			@hide="popper.onHide"
		>
			<template #reference>
				<div
					class="ai-mention__popper-picker"
					:style="{ top: popper.pos.top + 'px', left: popper.pos.left + 'px' }"
					:ref="setRefs('picker')"
				></div>
			</template>

			<div class="p-2 pb-0">
				<el-input v-model="popper.keyWord" :placeholder="t('搜索')" clearable />
			</div>

			<el-scrollbar max-height="200px">
				<div
					class="ai-mention__popper-list"
					:ref="setRefs('list')"
					:tabindex="0"
					@keydown="popper.onKeyDown"
				>
					<slot name="popper-prefix"> </slot>

					<div class="ai-mention__popper-group" v-for="(g, gi) in options" :key="gi">
						<el-text tag="p" class="p-[5px] text-[10px]" v-if="g.label">{{
							g.label
						}}</el-text>

						<div
							v-for="(item, index) in g.children"
							:key="index"
							class="ai-mention__popper-item"
							:class="{
								'is-focus': popper.focusKey == item.value
							}"
							@mousedown.stop.prevent="select(item)"
						>
							<slot name="popper-item" :item="item">
								{{ item.label }}
							</slot>
						</div>
					</div>

					<slot name="popper-suffix"> </slot>

					<div class="ai-mention__popper-empty" v-if="isEmpty(options)">
						{{ t('暂无数据') }}
					</div>
				</div>
			</el-scrollbar>
		</el-popover>
	</div>
</template>

<script setup lang="ts">
defineOptions({
	name: 'ai-mention'
});

import {
	computed,
	nextTick,
	onMounted,
	onUnmounted,
	reactive,
	watch,
	type PropType,
	ref
} from 'vue';
import Quill, { Delta } from 'quill';
import 'quill/dist/quill.core.css';
import { cloneDeep, debounce, isEmpty, isString } from 'lodash-es';
import { useI18n } from 'vue-i18n';
import { useCool } from '/@/cool';

interface Item {
	label?: string;
	value?: string;
	children?: Item[];
	[key: string]: any;
}

const props = defineProps({
	modelValue: String,
	// 高度
	height: {
		type: String,
		default: '80px'
	},
	// 弹窗选项
	options: {
		type: Array as PropType<Item[]>,
		default: () => []
	},
	// 占位符
	placeholder: String,
	// 触发前缀
	prefix: {
		type: String,
		default: '@'
	},
	// 值格式化
	valueFormatter: Function,
	// 标签格式化
	labelFormatter: Function,
	// 自定义弹窗类名
	popperClass: String,
	// 是否显示边框
	border: {
		type: Boolean,
		default: true
	},
	// 自定聚焦
	autoFocus: {
		type: Boolean,
		default: false
	},
	// 是否组模式
	isGroup: {
		type: Boolean,
		default: false
	}
});

const emit = defineEmits(['update:modelValue', 'change']);

const { refs, setRefs } = useCool();
const { t } = useI18n();

// 绑定值
const value = ref();

// 上次提及位置
let lastMentionPos = 0;

let quill: Quill | null;

// 获取 Embed 模块
const Embed = Quill.import('blots/embed') as any;

// 自定义 mention 格式
class MentionBlot extends Embed {
	static blotName = 'mention';
	static tagName = 'span';
	static className = `ai-mention__label`;

	static create(item: Item) {
		const node = super.create();

		// 存储数据
		node.setAttribute('data-label', item.label);
		node.setAttribute('data-value', item.value);

		// 禁止编辑
		node.contentEditable = 'false';

		// 设置标签
		node.innerHTML = item.label;

		return node;
	}

	static value(node: HTMLElement) {
		return {
			label: node.getAttribute('data-label'),
			value: node.getAttribute('data-value')
		};
	}
}

// 注册blot
if (!Quill.imports['formats/mention']) {
	Quill.register(MentionBlot);
}

// 获取文本
function getText(options: { isValue?: boolean; custom?: (item: Item) => string } = {}) {
	const { ops = [] } = quill?.getContents() || {};

	return ops
		.map(e => {
			if (isString(e.insert)) {
				return e.insert;
			}

			if (e.insert?.mention) {
				const { label, value } = e.insert.mention as Item;

				if (options.isValue) {
					if (props.valueFormatter) {
						return props.valueFormatter(e.insert.mention);
					} else {
						return `{${value}}`;
					}
				}

				if (options.custom) {
					return options.custom(e.insert.mention as Item);
				}

				return label;
			}

			return '';
		})
		.join('');
}

// 获取值
function getValue() {
	return getText({ isValue: true });
}

// 初始化
function init() {
	quill = new Quill(refs.editor, {
		placeholder: props.placeholder || t('请输入'),
		modules: {
			keyboard: {
				bindings: {
					list: {
						key: 'Enter',
						handler() {
							if (popper.visible) {
								popper.onKeyDown({ key: 'Enter' } as KeyboardEvent);
								return false;
							}

							return true;
						}
					}
				}
			}
		}
	});

	// 黏贴纯文本
	quill.clipboard.addMatcher(Node.ELEMENT_NODE, function (node, delta) {
		// 创建一个新的 Delta 对象
		const newDelta = new Delta();

		// 遍历原始 delta 的所有操作
		delta.ops.forEach(op => {
			newDelta.insert(op.insert as string);
		});

		return newDelta;
	});

	// 监听键盘事件
	quill.root.addEventListener('keydown', e => {
		if (e.key === 'Escape') {
			popper.close();
			e.preventDefault();
		}

		if (e.key == 'Backspace') {
			const range = quill?.getSelection();
			if (range && range.index > 0) {
				const char = quill?.getText(range.index - 1, 1);

				if (char === '') {
					quill?.deleteText(range.index - 1, 1);
				}
			}

			return;
		}

		if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
			if (popper.visible) {
				popper.onKeyDown(e);
				e.preventDefault();
			}
		}
	});

	// 监听文本变化
	quill.on(
		'text-change',
		debounce(() => {
			const position = quill?.getSelection()?.index;
			if (position === undefined) return;

			const text = getText({ custom: () => ' ' });

			if (text[position - 1] == props.prefix) {
				// 记录上次提及位置
				lastMentionPos = position;

				popper.open();
			} else {
				popper.close();
			}

			onChange();
		}),
		300
	);

	// 监听值变化
	watch(
		() => props.modelValue,
		val => {
			const text = getValue();

			if (text != val) {
				value.value = val;
				setContent(val);
			}
		},
		{
			immediate: true
		}
	);

	// 自动聚焦
	if (props.autoFocus) {
		// 避免和表单的自动聚焦冲突
		setTimeout(() => {
			focus();
		}, 10);
	}
}

// 设置内容
function setContent(text: string = '') {
	// 创建一个新的 Delta 对象
	const newDelta = new Delta();

	if (text) {
		// 正则表达式匹配 {value} 格式的内容
		const regex = /\{([^}]+)\}/g;
		let lastIndex = 0;
		let match;

		// 遍历所有匹配项
		while ((match = regex.exec(text)) !== null) {
			// 插入匹配前的普通文本
			if (match.index > lastIndex) {
				newDelta.insert(text.substring(lastIndex, match.index));
			}

			const value = match[1];
			// 在选项中查找匹配的值
			const option = list.value.find(item => item?.value === value);

			if (option) {
				// 找到匹配项，插入 Embed
				const data = { ...option };
				if (props.labelFormatter) {
					data.label = props.labelFormatter(option);
				} else {
					data.label = `${props.prefix}${option.label}`;
				}
				newDelta.insert({ mention: data });
			} else {
				// 未找到匹配项，作为普通文本插入
				newDelta.insert(match[0]);
			}

			lastIndex = match.index + match[0].length;
		}

		// 插入剩余的文本
		if (lastIndex < text.length) {
			newDelta.insert(text.substring(lastIndex));
		}
	}

	// 设置值
	quill?.setContents(newDelta);
}

// 监听文本变化
function onChange() {
	const val = getValue();
	value.value = val;

	emit('update:modelValue', val);
	emit('change', val);
}

// 销毁
function destroy() {
	quill = null;
}

// 聚焦
function focus() {
	setTimeout(() => {
		quill?.focus();
		quill?.setSelection((quill?.getLength() || 0) - 1, 0);
	}, 10);
}

// 选择
function select(item: Item) {
	const cursorPos = quill?.getSelection()?.index || 0;
	let mentionPos = cursorPos - 1;

	// 异常处理
	if (mentionPos < 0) {
		mentionPos = lastMentionPos;
	}

	// 删除@
	quill?.deleteText(mentionPos, 1);

	// 格式化标签
	const data = { ...item };

	if (props.labelFormatter) {
		data.label = props.labelFormatter(item);
	} else {
		data.label = `${props.prefix}${item.label}`;
	}

	// 在@的位置插入mention
	quill?.insertEmbed(mentionPos, 'mention', data, Quill.sources.USER);

	// 在mention后添加一个零宽字符，方便继续输入
	quill?.insertText(mentionPos + 1, '\u200B', Quill.sources.USER);

	// 将光标定位在零宽字符之后
	quill?.setSelection(mentionPos + 2, 0);

	// 关闭弹窗
	popper.close();

	// 触发文本变化
	onChange();
}

// 弹窗
const popper = reactive({
	visible: false,
	pos: { top: 0, left: 0 },
	keyWord: '',

	open: debounce(() => {
		popper.visible = true;
		popper.focusKey = list.value[0]?.value || '';
		popper.updatePos();

		nextTick(() => {
			refs.picker.click();
		});
	}, 100),

	close() {
		refs.popover?.hide();
	},

	onHide() {
		popper.visible = false;
	},

	updatePos() {
		const range = quill?.getSelection();
		if (range) {
			const bounds = quill?.getBounds(range.index);
			popper.pos = {
				top: (bounds?.top || 0) + 20,
				left: bounds?.left || 0
			};
		}
	},

	focusKey: '',
	onKeyDown(e: KeyboardEvent) {
		if (!popper.visible) {
			return;
		}

		let index = 0;

		const current = list.value.findIndex(e => popper.focusKey == e.value);

		switch (e.key) {
			case 'ArrowUp':
				index = current - 1;
				break;
			case 'ArrowDown':
				index = current + 1;
				break;
			case 'Enter':
				select(list.value[current]!);
				break;
		}

		const d = list.value[index];

		if (d) {
			popper.focusKey = d.value!;
		}
	}
});

// 选项列表
const options = computed(() => {
	let list = [] as Item[];

	if (props.isGroup) {
		list = props.options;
	} else {
		list = [
			{
				children: props.options
			}
		];
	}

	return cloneDeep(list).filter(e => {
		e.children = (e.children || []).filter(a =>
			popper.keyWord ? a.label?.includes(popper.keyWord) : true
		);

		return !isEmpty(e.children);
	});
});

// 数据列表
const list = computed(() => {
	return props.options.map(e => e.children || []).flat();
});

onMounted(() => {
	init();
});

onUnmounted(() => {
	destroy();
});

defineExpose({
	getText,
	select,
	focus,
	destroy
});
</script>

<style lang="scss">
.ai-mention {
	width: 100%;
	height: 100%;
	position: relative;

	&.is-border {
		border: 1px solid var(--el-border-color);
		border-radius: var(--el-border-radius-base);
	}

	&__editor {
		width: 100%;
		height: 100%;

		.ql-editor {
			padding: 11px;
			line-height: 22px;

			&.ql-blank::before {
				color: var(--el-text-color-placeholder);
				font-style: normal;
			}
		}
	}

	&__label {
		color: #fff;
		background-color: var(--el-color-primary);
		white-space: nowrap;
		cursor: pointer;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		user-select: all;
		border-radius: 3px;
		padding: 0 5px;
		font-size: 12px;
		margin: 0 2px;
		height: 20px;
	}

	&__popper {
		&-picker {
			height: 1px;
			width: 1px;
			position: absolute;
			z-index: 99;
		}

		&-list {
			padding: 5px;
		}

		&-item {
			padding: 8px 12px;
			cursor: pointer;
			font-size: 14px;
			border-radius: var(--el-border-radius-base);

			&:hover {
				background-color: var(--el-fill-color-lighter);
			}

			&.is-focus {
				background-color: var(--el-fill-color-light);
			}
		}

		&-empty {
			padding: 10px;
			text-align: center;
			color: var(--el-text-color-placeholder);
		}
	}
}
</style>
