<script setup lang="ts">
import { computed, inject, ref, watch, type Ref } from 'vue'
import { LexicalComposer } from 'lexical-vue/LexicalComposer'
import { ContentEditable } from 'lexical-vue/LexicalContentEditable'
import { HistoryPlugin } from 'lexical-vue/LexicalHistoryPlugin'
import { RichTextPlugin } from 'lexical-vue/LexicalRichTextPlugin'
import { OnChangePlugin } from 'lexical-vue/LexicalOnChangePlugin'
import { $getRoot } from 'lexical'
import { textToEditorState } from './utils'
import VarLabelNodePlugin from './plugins/VarLabelBlock.vue'
import VarLaberPickerPlugin from './plugins/VarLaberPickerPlugin.vue'
import { syncPromptEditorVarLabelNodeVars, VarLabelNode } from './plugins/VarLabel'

import type { PromptEditorMenuOption } from './plugins/VarLaberPickerPlugin.vue'
import type { InitialConfigType } from 'lexical-vue'
import type { NodeVar } from '@/types/var'

const props = withDefaults(
	defineProps<{
		modelValue?: string
		placeholder?: string
		autoFocus?: boolean
		triggerStrings?: string[]
		menuOptions?: PromptEditorMenuOption[]
		// 默认行数，用于控制最小高度；大于 1 时表现为多行输入
		rows?: number
	}>(),
	{
		modelValue: '',
		placeholder: '',
		autoFocus: false,
		triggerStrings: () => ['/'],
		menuOptions: () => [],
		rows: 1
	}
)

const emit = defineEmits<{
	'update:modelValue': [value: string]
	focus: [event: FocusEvent]
	blur: [event: FocusEvent]
}>()
const contextId = `prompt-editor-${Math.random().toString(36).slice(2, 10)}`

const editorConfig: InitialConfigType = {
	namespace: 'PromptEditor',
	theme: {
		ltr: 'ltr',
		rtl: 'rtl',
		placeholder: 'editor-placeholder',
		paragraph: 'editor-paragraph'
	},
	editable: true,
	editorState: textToEditorState(props.modelValue),
	onError(error: Error) {
		throw error
	},
	nodes: [VarLabelNode]
}

const isFocused = ref(false)
const nodeVars = inject<Ref<NodeVar[]>>('nodeVars', ref([]))

watch(
	nodeVars,
	(value) => {
		syncPromptEditorVarLabelNodeVars(contextId, value || [])
	},
	{
		immediate: true,
		deep: true
	}
)

const fallbackMenuOptions = computed<PromptEditorMenuOption[]>(() => {
	return (nodeVars?.value || []).flatMap((group) => {
		return group.variableList.map((variable) => {
			const value = variable.expression.startsWith('#{')
				? variable.expression
				: `#{${variable.expression}}`
			const type = group.id === 'env' ? 'env' : group.id === 'sys' ? 'sys' : 'node'

			return {
				key: `${group.id}-${variable.expression}`,
				label: variable.name,
				value,
				type,
				groupId: group.id,
				groupName: group.name,
				nodeType: group.type,
				valueType: variable.type
			}
		})
	})
})

const pickerOptions = computed<PromptEditorMenuOption[]>(() => {
	if (props.menuOptions.length) return props.menuOptions
	return fallbackMenuOptions.value
})

const wrapperStyle = computed(() => {
	const rows = Number(props.rows || 1)
	if (rows > 1) {
		// 每行约 30px 行高，稍微加一点边距
		return {
			minHeight: `${rows * 30 + 10}px`
		}
	}
	return {}
})

const isMultiline = computed(() => (props.rows || 1) > 1)

function onFocus(e: FocusEvent) {
	isFocused.value = true
	emit('focus', e)
}

function onBlur(e: FocusEvent) {
	isFocused.value = false
	emit('blur', e)
}

function onChange(editorState: any) {
	const text = editorState.read(() => {
		return $getRoot()
			.getChildren()
			.map((p) => p.getTextContent())
			.join('\n')
	})

	emit('update:modelValue', text)
}
</script>

<template>
	<LexicalComposer :initial-config="editorConfig">
		<div
			class="el-input__wrapper"
			:class="{ 'is-focus': isFocused, 'is-multiline': isMultiline }"
			:style="wrapperStyle"
			:data-prompt-editor-context-id="contextId"
		>
			<RichTextPlugin>
				<template #contentEditable>
					<ContentEditable class="el-input__inner" @focus="onFocus" @blur="onBlur">
						<template #placeholder>
							<div class="editor-placeholder" :style="{ marginTop: `${rows > 1 ? 6 : 0}px` }">
								{{ placeholder }}
							</div>
						</template>
					</ContentEditable>
				</template>
			</RichTextPlugin>
		</div>
		<HistoryPlugin />
		<OnChangePlugin :onChange="onChange" />
		<VarLaberPickerPlugin :trigger-strings="triggerStrings" :options="pickerOptions" />
		<VarLabelNodePlugin />
		<slot></slot>
	</LexicalComposer>
</template>

<style lang="less" scoped>
.ltr {
	text-align: left;
}

.rtl {
	text-align: right;
}

.el-input__wrapper {
	width: 100%;
	box-sizing: border-box;
	&.is-focus,
	&:hover {
		box-shadow: 0 0 0 1px var(--el-color-primary) inset;
	}
	&.is-multiline {
		align-items: flex-start;
		padding-top: 6px;
		padding-bottom: 6px;
	}
}

:deep(.el-input__inner) {
	text-align: left;
	min-height: 30px;
	resize: none;
	font-size: 15px;
	caret-color: rgb(5, 5, 5);
	position: relative;
	tab-size: 1;
	outline: 0;
	caret-color: #444;

	p {
		margin: 0;
		line-height: 30px;
	}
}

:deep(.var-label-token) {
	display: inline-flex;
	align-items: center;
	margin: 0 2px;
	vertical-align: middle;
	position: relative;
	top: -1px;
	line-height: 1;
	max-width: 100%;
}

:deep(.var-label-token .var-label) {
	display: inline-flex;
	align-items: center;
	max-width: 100%;
	vertical-align: middle;
}

.editor-placeholder {
	line-height: 30px;
	color: #a8abb2;
	overflow: hidden;
	position: absolute;
	text-overflow: ellipsis;
	top: 0;
	left: 10px;
	font-size: 14px;
	user-select: none;
	display: inline-block;
	pointer-events: none;
}
</style>
