<template>
	<div class="setter-chat">
		<MessageList
			ref="messageListRef"
			:messages="messages"
			:loading="isRunning"
			@retry="handleRetry"
			:can-add-to-kb="false"
		>
			<template #message-extra="{ item }">
				<WorkflowTraceBubble v-if="props.showWorkflowTrace" :message="item" />
			</template>
		</MessageList>

		<div class="setter-chat__footer">
			<el-dialog
				v-model="uploadDialogVisible"
				title="上传文件"
				width="560px"
				append-to-body
				:close-on-click-modal="false"
			>
				<FileUploadInput
					v-model="attachments"
					:multiple="true"
					:allow-link-input="false"
					tip="上传后会作为本次 AI 对话的 files 参数。"
				/>
				<template #footer>
					<el-button @click="uploadDialogVisible = false">关闭</el-button>
					<el-button type="primary" @click="uploadDialogVisible = false">完成</el-button>
				</template>
			</el-dialog>

			<ChatInput
				v-model="senderValue"
				:loading="isRunning"
				:attachments="attachments"
				@submit="handleSend"
				@cancel="handleCancel"
			>
				<template #prefix-extra>
					<el-badge :value="attachments.length" :hidden="!attachments.length">
						<el-button round plain color="#626aef" @click="uploadDialogVisible = true">
							<el-icon>
								<Paperclip />
							</el-icon>
						</el-button>
					</el-badge>
				</template>
			</ChatInput>
		</div>
	</div>
</template>

<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Paperclip } from '@element-plus/icons-vue'
import { agent } from '@repo/api-service'

import ChatInput from '@/components/Chat/ChatInput.vue'
import MessageList from '@/components/Chat/MessageList.vue'
import FileUploadInput from '@/features/fileUpload/FileUploadInput.vue'
import WorkflowTraceBubble from './WorkflowTraceBubble.vue'
import { useRunnerStore } from '@/store/modules/runner.store'
import { cloneDeep } from 'lodash-es'

import type { WorkflowUploadFile } from '@/features/fileUpload/shared'
import type {
	BubbleMessage,
	ChatReference,
	ChatSseMessage,
	ChatToolCall,
	ChatToolResult
} from '@/views/chat/types'
import type { IWorkflowNode } from '@repo/workflow'

interface Props {
	node?: IWorkflowNode
	workflowId?: string
	baseParams?: Record<string, any>
	showWorkflowTrace?: boolean
	validateBeforeSend?: () => boolean | Record<string, any> | Promise<boolean | Record<string, any>>
	onFirstSend?: () => void
}

const props = withDefaults(defineProps<Props>(), {
	baseParams: () => ({}),
	showWorkflowTrace: false,
	validateBeforeSend: undefined,
	onFirstSend: undefined
})

const emit = defineEmits<{
	'run-started': [nodeId: string]
}>()

const runnerStore = useRunnerStore()
const messages = ref<BubbleMessage[]>([])
const senderValue = ref('')
const attachments = ref<WorkflowUploadFile[]>([])
const uploadDialogVisible = ref(false)
const messageListRef = ref<InstanceType<typeof MessageList>>()
const activeAiMessage = ref<BubbleMessage | null>(null)
const handledChatMessageCount = ref(0)
const startingRunner = ref(false)

const isRunning = computed(
	() =>
		startingRunner.value ||
		(!!activeAiMessage.value &&
			(runnerStore.status === 'connecting' || runnerStore.status === 'running'))
)

const appAgentId = computed(
	() => props.workflowId || props.node?.appAgentId || (props.node?.data as any)?.appAgentId || ''
)

const scrollToBottom = () => {
	nextTick(() => {
		messageListRef.value?.scrollToBottom?.()
	})
}

const normalizeText = (value?: unknown) => `${value || ''}`.trim()

const createMessageKey = (prefix: string) =>
	`${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`

const createUserMessage = (content: string, files: WorkflowUploadFile[] = []): BubbleMessage => ({
	key: createMessageKey('user'),
	role: 'user',
	placement: 'end',
	content,
	loading: false,
	shape: 'corner',
	variant: 'outlined',
	isMarkdown: true,
	typing: false,
	isFog: false,
	message_files: files.map((item) => item.path || item.id).filter(Boolean)
})

const createAiMessage = (): BubbleMessage => ({
	key: createMessageKey('ai'),
	role: 'ai',
	placement: 'start',
	content: '',
	answerText: '',
	thinking: '',
	toolCalls: [],
	toolResults: [],
	references: [],
	assistantMessageId: '',
	loading: true,
	shape: 'corner',
	variant: 'filled',
	isMarkdown: true,
	typing: { step: 3, interval: 25 },
	isFog: true,
	...(props.showWorkflowTrace
		? {
				workflowTraceVisible: true,
				workflowTraceNodes: []
			}
		: {})
})

const syncWorkflowTraceToActiveMessage = () => {
	if (!props.showWorkflowTrace || !activeAiMessage.value) return
	activeAiMessage.value.workflowTraceNodes = cloneDeep(runnerStore.nodes)
}

const getFilesParam = () =>
	attachments.value
		.map((item) => ({
			id: item.id,
			name: item.name,
			extensionName: item.extensionName,
			size: item.size,
			path: item.path || item.id
		}))
		.filter((item) => item.path)

const normalizeToolCall = (event: ChatSseMessage): ChatToolCall => {
	const data = event.data || {}
	return {
		id: data.id || event.id,
		toolCallId: data.tool_call_id || data.toolCallId,
		toolName: data.tool_name || data.toolName,
		arguments: data.arguments || data.params || data.args || {},
		content: event.content || data.content
	}
}

const normalizeToolResult = (event: ChatSseMessage): ChatToolResult => {
	const data = event.data || {}
	return {
		id: data.id || event.id,
		toolCallId: data.tool_call_id || data.toolCallId,
		toolName: data.tool_name || data.toolName,
		success: data.success,
		error: data.error,
		output: data.output,
		thought: data.thought,
		durationMs: data.duration_ms || data.durationMs,
		displayType: data.display_type || data.displayType,
		contentItems: data.content_items || data.contentItems
	}
}

const normalizeReferences = (event: ChatSseMessage): ChatReference[] => {
	const data = event.data || {}
	const eventRefs = (event as ChatSseMessage & { references?: unknown }).references
	const refs = data.knowledge_references || data.references || eventRefs || []
	if (!Array.isArray(refs)) return []

	return refs.map((item: Record<string, any>) => ({
		id: item.id,
		knowledgeBaseId: item.knowledge_base_id || item.knowledgeBaseId,
		knowledgeId: item.knowledge_id || item.knowledgeId,
		knowledgeTitle: item.knowledge_title || item.knowledgeTitle,
		knowledgeFilename: item.knowledge_filename || item.knowledgeFilename,
		knowledgeDescription: item.knowledge_description || item.knowledgeDescription,
		content: item.content,
		matchedContent: item.matched_content || item.matchedContent,
		score: item.score,
		chunkType: item.chunk_type || item.chunkType
	}))
}

const normalizeChatEvent = (raw: Record<string, any>): ChatSseMessage => {
	const data = raw.data && typeof raw.data === 'object' ? raw.data : raw
	const content =
		raw.content ??
		raw.answer ??
		raw.output ??
		raw.delta ??
		raw.text ??
		data.content ??
		data.answer ??
		''

	return {
		id: raw.id || data.id,
		response_type:
			raw.response_type ||
			raw.responseType ||
			raw.type ||
			raw.event ||
			(content ? 'agent_query' : 'complete'),
		content,
		done: raw.done || raw.is_end || raw.finished,
		data
	}
}

const buildReferenceMarkdown = (references: ChatReference[] = []) => {
	if (!references.length) return ''

	const items = references.map((item, index) => {
		const title =
			item.knowledgeTitle || item.knowledgeFilename || item.knowledgeId || `引用 ${index + 1}`
		const content = item.matchedContent || item.content || ''
		return `\n${index + 1}. ${title}${content ? `\n   ${content}` : ''}`
	})

	return `\n\n**引用**${items.join('')}`
}

const updateMessageContent = (message: BubbleMessage) => {
	const answer = normalizeText(message.answerText || message.content)
	message.content = `${answer}${buildReferenceMarkdown(message.references)}`
}

const finishActiveMessage = (error?: string) => {
	const message = activeAiMessage.value
	if (!message) return

	if (error && !message.content && !message.answerText) {
		message.content = error
		message.answerText = error
		message.error = true
	}

	message.loading = false
	message.isFog = false
	message.streamCompleted = true
	updateMessageContent(message)
	activeAiMessage.value = null
	scrollToBottom()
}

const applyStructuredEventToMessage = (message: BubbleMessage, event: ChatSseMessage) => {
	const data = event.data || {}

	switch (event.response_type) {
		case 'answer':
			message.assistantMessageId =
				data.assistant_message_id || data.assistantMessageId || message.assistantMessageId
			if (event.content) {
				message.answerText = `${message.answerText || ''}${event.content}`
			}
			break
		case 'agent_query':
		case 'message':
		case 'delta':
			message.assistantMessageId =
				data.assistant_message_id || data.assistantMessageId || message.assistantMessageId
			break
		case 'thinking': {
			const thought = normalizeText(event.content || data.thought || data.content)
			if (thought) {
				message.thinking = `${message.thinking || ''}${message.thinking ? '\n\n' : ''}${thought}`
			}
			break
		}
		case 'tool_call':
			message.toolCalls = [...(message.toolCalls || []), normalizeToolCall(event)]
			break
		case 'tool_result': {
			message.toolResults = [...(message.toolResults || []), normalizeToolResult(event)]
			const thought = normalizeText(data.thought)
			if (thought) {
				message.thinking = `${message.thinking || ''}${message.thinking ? '\n\n' : ''}${thought}`
			}
			break
		}
		case 'references':
			message.references = [...(message.references || []), ...normalizeReferences(event)]
			break
		case 'complete':
			break
		default:
			break
	}

	updateMessageContent(message)
	message.loading = event.response_type !== 'complete' && !event.done
	message.isFog = false

	if (event.response_type === 'complete' || event.done) {
		message.streamCompleted = true
	}
}

const handleRunnerChatMessages = () => {
	const list = runnerStore.agentChatMessages
	if (handledChatMessageCount.value >= list.length) return

	const nextMessages = list.slice(handledChatMessageCount.value)
	handledChatMessageCount.value = list.length

	if (!activeAiMessage.value) return

	nextMessages.forEach((item) => {
		applyStructuredEventToMessage(activeAiMessage.value!, normalizeChatEvent(item.msg || {}))
	})
	scrollToBottom()
}

const handleSend = async (content?: string) => {
	const query = normalizeText(content)
	if (!query) {
		ElMessage.warning('请输入问题')
		return
	}
	if (!props.node?.id || !appAgentId.value) {
		ElMessage.warning('缺少智能体或节点信息')
		return
	}
	if (isRunning.value) return

	let executeParams = cloneDeep(props.baseParams)
	if (props.validateBeforeSend) {
		const passed = await props.validateBeforeSend()
		if (!passed) return
		if (typeof passed === 'object') {
			executeParams = cloneDeep(passed)
		}
	}

	const files = getFilesParam()
	messages.value.push(createUserMessage(query, attachments.value))
	const aiMessage = createAiMessage()
	messages.value.push(aiMessage)
	activeAiMessage.value = aiMessage
	senderValue.value = ''
	attachments.value = []
	handledChatMessageCount.value = 0
	scrollToBottom()

	startingRunner.value = true
	try {
		const response = await agent.postAgentDoExecute({
			appAgentId: appAgentId.value,
			start_node_id: props.node.id,
			is_debugger: true,
			responseType: 'ws',
			query,
			files: files.map((item) => item.id),
			params: {
				...executeParams
			}
		})

		const agentRunnerKey = response?.result
		if (!agentRunnerKey) {
			finishActiveMessage('智能体启动失败')
			ElMessage.error('智能体启动失败')
			return
		}

		runnerStore.startRunner(agentRunnerKey, props.node.id)
		syncWorkflowTraceToActiveMessage()
		props.onFirstSend?.()
		emit('run-started', props.node.id)
		handledChatMessageCount.value = 0
	} catch (error) {
		console.error('postAgentDoExecute error', error)
		finishActiveMessage('智能体运行失败')
		ElMessage.error('智能体运行失败')
	} finally {
		startingRunner.value = false
	}
}

const handleCancel = () => {
	runnerStore.stopRunner()
	finishActiveMessage()
}

const handleRetry = (message: BubbleMessage) => {
	const index = messages.value.findIndex((item) => item === message)
	if (index <= 0) return
	const previous = messages.value[index - 1]
	if (previous?.role !== 'user') return
	handleSend(previous.content)
}

const resetConversation = () => {
	runnerStore.resetRunner()
	messages.value = []
	senderValue.value = ''
	attachments.value = []
	uploadDialogVisible.value = false
	activeAiMessage.value = null
	handledChatMessageCount.value = 0
	startingRunner.value = false
}

defineExpose({
	scrollToBottom,
	resetConversation
})

watch(
	() => runnerStore.agentChatMessages.length,
	() => {
		handleRunnerChatMessages()
	}
)

watch(
	() => runnerStore.nodes.map((node) => ({ ...node })),
	() => {
		syncWorkflowTraceToActiveMessage()
		scrollToBottom()
	},
	{ deep: true }
)

watch(
	() => runnerStore.status,
	(status) => {
		if (!activeAiMessage.value) return
		if (status === 'finished') {
			finishActiveMessage()
		}
		if (status === 'error') {
			finishActiveMessage(runnerStore.errorMsg || '智能体运行失败')
		}
		if (status === 'suspended') {
			finishActiveMessage('智能体运行已挂起')
		}
	}
)
</script>

<style lang="less" scoped>
.setter-chat {
	height: 100%;
	min-height: 0;
	display: flex;
	flex-direction: column;
	background: var(--bg-page);
	overflow: hidden;
}

.setter-chat__footer {
	flex-shrink: 0;
}

:deep(.chat-content) {
	min-height: 0;
	padding: 12px;
}

:deep(.sender-wrapper) {
	width: calc(100% - 24px);
	margin-bottom: 0;
	padding: 10px 0;
}

:deep(.el-bubble-end .el-bubble-content) {
	background-color: var(--el-color-primary-light-9);
	border-radius: 8px 0 8px 8px;
	color: var(--text-primary);
	border: none;
}

:deep(.el-bubble-start .el-bubble-content) {
	background-color: var(--bg-container);
	border: none;
	color: var(--text-primary);
	width: 100%;
	max-width: min(920px, calc(100vw - 220px));
}

:deep(.el-sender-footer) {
	border: none;
}
</style>
