<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { aiModel } from '@repo/api-service'
import { Icon, IconButton } from '@repo/ui'

import NodeRuntimeConfig from '@/nodes/_base/NodeRuntimeConfig.vue'
import VarSelect from '@/nodes/_base/VarSelect.vue'
import VarInput from '@/nodes/_base/VarInput.vue'
import { useSetterModel } from '../_shared/useSetterModel'
import PromptModal from '@/features/PromptModal.vue'
import { DocumentCopy } from '@element-plus/icons-vue'
import StructModal from './StructModal.vue'

import type { LLMData, LLMPromptItem } from './index'

type LLMModelItem = NonNullable<
	Awaited<ReturnType<typeof aiModel.postModelPageList>>['result']
>['model'][number]

const props = defineProps<{
	data: LLMData
}>()

const emit = defineEmits<{
	(e: 'update', data: LLMData): void
}>()

const formData = useSetterModel<LLMData>(props, emit)
const modelsLoading = ref(false)
const chatModels = ref<LLMModelItem[]>([])
const promptTemplateDialogVisible = ref(false)
const editPrompt = ref<LLMPromptItem>()
/** TODO: 提示词编辑器双向绑定存在问题 */
const editKey = ref(0)
const structModalVisible = ref(false)

interface SchemaPreviewNode {
	id: string
	name: string
	type: string
	description: string
	required: boolean
	enumValues: Array<string | number | boolean>
	children: SchemaPreviewNode[]
}

// 打开 Schema 编辑器
const openSchemaDialog = () => {
	structModalVisible.value = true
}

// 关闭 Schema 编辑器
const handleSchemaClose = () => {
	structModalVisible.value = false
}

const modelLabel = (item: LLMModelItem) => (item.title ? `${item.title} (${item.name})` : item.name)

const formatSchemaType = (schema: any) => {
	if (schema?.type === 'array') {
		const itemType = schema.items?.type || 'string'
		return `array[${itemType}]`
	}
	return schema?.type || 'string'
}

const buildSchemaPreviewTree = (
	schema: any,
	parentPath = 'structured_output'
): SchemaPreviewNode[] => {
	if (!schema?.properties) return []

	const required = new Set<string>(Array.isArray(schema.required) ? schema.required : [])

	return Object.entries(schema.properties).map(([name, value]) => {
		const item = value as any
		const isArrayObject = item.type === 'array' && item.items?.type === 'object'
		const childSchema = item.type === 'object' ? item : isArrayObject ? item.items : null
		const currentPath = `${parentPath}.${name}`

		return {
			id: currentPath,
			name,
			type: formatSchemaType(item),
			description: item.description || '',
			required: required.has(name),
			enumValues: Array.isArray(item.enum) ? item.enum : [],
			children: childSchema?.properties ? buildSchemaPreviewTree(childSchema, currentPath) : []
		}
	})
}

const structuredPreviewTree = computed(() =>
	buildSchemaPreviewTree(formData.value.structured_output)
)

const fetchChatModels = async (keyword = '') => {
	const res = await aiModel.postModelPageList({
		keyword: keyword.trim(),
		type: 'KnowledgeQA',
		source: '',
		pageIndex: 1,
		pageSize: 20
	})
	if (res?.isSuccess) {
		const nextModels = (res.result?.model || []) as LLMModelItem[]
		chatModels.value = nextModels
	}
}

const searchChatModels = async (keyword: string) => {
	modelsLoading.value = true
	try {
		await fetchChatModels(keyword)
	} finally {
		modelsLoading.value = false
	}
}

/**
 * 提示词模版选择
 * @param msg
 */
function openPromptTemplatePicker(msg: LLMPromptItem) {
	editPrompt.value = msg
	promptTemplateDialogVisible.value = true
}

/**
 * 绑定提示词模版
 * @param content
 */
function handlePromptTemplateConfirm(content: string) {
	if (editPrompt.value) {
		editPrompt.value.content = content
		promptTemplateDialogVisible.value = false
		editKey.value++
	}
}

onMounted(async () => {
	await searchChatModels('')
})
</script>

<template>
	<el-scrollbar class="w-full box-border p-12px">
		<div class="llm-setter">
			<section class="section-block">
				<div class="section-title-row">
					<label class="section-title">模型 <span class="text-#f04438">*</span></label>
				</div>
				<div class="field-row">
					<el-select
						v-model="formData.model_id"
						filterable
						remote
						reserve-keyword
						clearable
						class="w-full"
						:remote-method="searchChatModels"
						:loading="modelsLoading"
						placeholder="请选择模型"
					>
						<el-option
							v-for="model in chatModels"
							:key="model.id"
							:label="modelLabel(model)"
							:value="model.id"
						/>
					</el-select>
					<div class="field-hint">选择用于该节点的大语言模型。</div>
				</div>
			</section>

			<section class="section-block">
				<div class="section-title-row">
					<label class="section-title">上下文</label>
				</div>
				<VarInput
					v-model="formData.context"
					:rows="3"
					class="w-full"
					placeholder="请输入，输入/选择变量"
				/>
			</section>

			<section class="section-block">
				<div class="section-header">
					<label class="section-title">提示词</label>
				</div>
				<div class="message-list">
					<div
						v-for="(message, index) in formData.prompt_template"
						:key="`${message.role}-${index}`"
						class="message-card"
					>
						<div class="message-card__header">
							<div class="message-role-row">
								<el-select
									v-model="message.role"
									:disabled="!index"
									class="role-select"
									size="small"
								>
									<el-option label="SYSTEM" value="system" disabled />
									<el-option label="USER" value="user" />
									<el-option label="ASSISTANT" value="assistant" />
								</el-select>
								<el-button
									class="prompt-template-trigger"
									text
									:icon="DocumentCopy"
									@click="openPromptTemplatePicker(message)"
								>
									模板选择
								</el-button>
							</div>
							<IconButton
								link
								icon="lucide:trash-2"
								class="text-#f04438"
								v-if="index"
								@click="formData.prompt_template.splice(index, 1)"
							/>
						</div>
						<VarInput
							:key="editKey"
							v-model="message.content"
							class="message-editor"
							:rows="4"
							placeholder="在这里写你的提示词，输入输入 '/' 插入变量"
						/>
					</div>
				</div>
				<el-button
					class="add-message-btn"
					text
					@click="formData.prompt_template.push({ role: 'user', content: '' })"
				>
					+ 添加消息
				</el-button>
			</section>

			<section class="section-block">
				<el-collapse>
					<el-collapse-item title="" name="2">
						<template #title>
							<div class="flex items-center justify-between beautify">
								<label class="text-14px font-bold text-gray-700">输出变量</label>
							</div>
						</template>
						<ul>
							<li v-for="output in formData.outputs" :key="output.name">
								<div>
									<span class="text-#333">{{ output.name }}</span>
									<span class="text-#999 ml-8px">{{ output.type }}</span>
								</div>
								<div class="text-#666">{{ output.describe }}</div>
							</li>
						</ul>
					</el-collapse-item>
				</el-collapse>

				<div class="structured-block">
					<span class="flex items-center gap-12px text-12px text-text-secondary">
						<el-switch v-model="formData.structured_output_enabled" />
						<span>结构化输出</span>
					</span>

					<div v-if="formData.structured_output_enabled">
						<div class="structured-block__header">
							<div class="structured-block__title">structured_output</div>
							<el-button text @click="openSchemaDialog">
								<el-icon><Icon icon="lucide:pencil" /></el-icon>
								配置
							</el-button>
						</div>
						<div class="structured-preview">
							<el-tree
								v-if="structuredPreviewTree.length"
								:data="structuredPreviewTree"
								node-key="id"
								default-expand-all
								expand-on-click-node="false"
								class="schema-tree"
							>
								<template #default="{ data }">
									<div class="schema-tree-node">
										<div class="schema-tree-node__main">
											<span class="schema-tree-node__name">{{ data.name }}</span>
											<span class="schema-tree-node__type">{{ data.type }}</span>
											<span v-if="data.required" class="schema-tree-node__required">必填</span>
										</div>
										<div v-if="data.description" class="schema-tree-node__desc">
											{{ data.description }}
										</div>
										<div v-if="data.enumValues.length" class="schema-tree-node__enum">
											{{
												data.enumValues
													.map((item: string | number | boolean) => JSON.stringify(item))
													.join(' | ')
											}}
										</div>
									</div>
								</template>
							</el-tree>
							<el-empty v-else description="暂无结构化字段" :image-size="60" />
						</div>
					</div>
				</div>
			</section>

			<NodeRuntimeConfig v-model="formData" />
		</div>
	</el-scrollbar>

	<PromptModal
		v-model="promptTemplateDialogVisible"
		:field="editPrompt?.role === 'system' ? 'systemPrompt' : 'contextTemplate'"
		mode=""
		@confirm="handlePromptTemplateConfirm"
	/>

	<StructModal
		:visible="structModalVisible"
		v-model:modelValue="formData.structured_output"
		@close="handleSchemaClose"
	/>
</template>

<style scoped lang="less">
.llm-setter {
	display: flex;
	flex-direction: column;
	gap: 16px;
}

.section-block {
	display: flex;
	flex-direction: column;
	gap: 10px;
	padding-bottom: 16px;
	border-bottom: 1px solid #eef2f7;
}

.section-title-row,
.section-header,
.section-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
}

.section-title {
	font-size: 14px;
	font-weight: 700;
	color: #344054;
}

.field-row {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.field-hint {
	font-size: 12px;
	color: #667085;
}

.message-list {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.message-card {
	display: flex;
	flex-direction: column;
	gap: 10px;
	padding: 14px;
	border: 1px solid #eaecf0;
	border-radius: 12px;
	background: #f9fafb;
}

.message-card__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
}

.message-role-row {
	display: flex;
	align-items: center;
	gap: 10px;
}

.role-select {
	width: 128px;
}

.message-badge {
	font-size: 12px;
	color: #98a2b3;
}

.message-editor {
	:deep(.el-input__wrapper) {
		border-radius: 10px;
	}
}

.add-message-btn {
	width: 100%;
	justify-content: center;
	font-size: 14px;
	font-weight: 600;
	background: #f2f4f7;
	border-radius: 10px;
}

.structured-block {
	display: flex;
	flex-direction: column;
	gap: 10px;
	padding: 12px;
	border: 1px solid #eaecf0;
	border-radius: 12px;
	background: #fff;
}

.structured-block__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
}

.structured-block__title {
	font-size: 13px;
	font-weight: 700;
	color: #344054;
}

.structured-preview {
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.schema-tree {
	--el-tree-node-content-height: auto;
	--el-tree-node-hover-bg-color: transparent;
	background: #fff;
	border: 1px solid #eaecf0;
	border-radius: 12px;
	padding: 6px 8px;
}

.schema-tree :deep(.el-tree-node__content) {
	height: auto;
	padding: 8px 0;
	background: transparent;
	align-items: flex-start;
}

.schema-tree :deep(.el-tree-node__expand-icon) {
	margin-top: 3px;
	color: #98a2b3;
}

.schema-tree :deep(.el-tree-node__children) {
	padding-left: 18px;
}

.schema-tree-node {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
	padding: 2px 0;
}

.schema-tree-node__main {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 8px;
}

.schema-tree-node__name {
	font-size: 15px;
	font-weight: 700;
	color: #344054;
}

.schema-tree-node__type {
	font-size: 13px;
	color: #667085;
}

.schema-tree-node__required {
	font-size: 12px;
	font-weight: 700;
	color: #f97316;
}

.schema-tree-node__desc {
	margin-left: 0;
	font-size: 13px;
	line-height: 1.5;
	color: #667085;
	word-break: break-word;
}

.schema-tree-node__enum {
	font-size: 12px;
	line-height: 1.4;
	color: #98a2b3;
	word-break: break-word;
}
</style>
