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

import { useI18n } from '@/composables/useI18n'
import { useRunnerStore, type RunnerNodeState } from '@/store/modules/runner.store'

interface Props {
	node: IWorkflowNode | undefined
	active?: boolean
}

type NodeLastRunnerLog = Awaited<ReturnType<typeof agent.postAgentGetAgentNodeLastRunnerLogs>>['result']

const props = withDefaults(defineProps<Props>(), {
	active: false
})
const { t } = useI18n()
const runnerStore = useRunnerStore()
const loading = ref(false)
const errorMessage = ref('')
const nodeLog = ref<NodeLastRunnerLog | null>(null)
let latestRequestId = 0

const toNodeLastRunnerLog = (node: RunnerNodeState): NodeLastRunnerLog => {
	const track = node.track || null
	const isSuccess =
		typeof track?.is_success === 'boolean'
			? track.is_success
			: node.status === 'success'

	return {
		appAgentId: '',
		appAgentNodeId: node.nodeId,
		appAgentRunnerId: runnerStore.currentRunnerKey || '',
		creationTime: node.lastUpdateTime || '',
		errorMsg: node.status === 'failed' ? String(track?.errorMsg || track?.error || '') : '',
		id: '',
		isDeleted: false,
		raw: track
			? {
					...track,
					agent_node_id: track.agent_node_id || node.nodeId,
					is_success: isSuccess,
					node_name: track.node_name || node.nodeName,
					node_type: track.node_type || node.nodeType
				}
			: null,
		status: node.status,
		updateTime: node.lastUpdateTime || '',
		useTime: Number(track?.use_time ?? track?.useTime ?? 0)
	} as NodeLastRunnerLog
}

const findRunnerNodeState = (nodeId: string) => {
	const currentExecution = runnerStore.currentRunnerKey
		? runnerStore.executions.find((item) => item.runnerKey === runnerStore.currentRunnerKey)
		: null

	return (
		currentExecution?.nodes.find((item) => item.nodeId === nodeId) ||
		runnerStore.nodes.find((item) => item.nodeId === nodeId) ||
		runnerStore.executions.find((execution) =>
			execution.nodes.some((item) => item.nodeId === nodeId)
		)?.nodes.find((item) => item.nodeId === nodeId) ||
		null
	)
}

const isCurrentNodeRunning = (nodeId: string) => {
	const runnerRunning = runnerStore.status === 'connecting' || runnerStore.status === 'running'
	if (!runnerRunning) return false

	const runnerNodeState = findRunnerNodeState(nodeId)
	if (runnerNodeState) return runnerNodeState.status === 'running'

	return runnerStore.currentStartNodeId === nodeId
}

const createRunningNodeLog = (nodeId: string): NodeLastRunnerLog =>
	({
		appAgentId: '',
		appAgentNodeId: nodeId,
		appAgentRunnerId: runnerStore.currentRunnerKey || '',
		creationTime: '',
		errorMsg: '',
		id: '',
		isDeleted: false,
		raw: null,
		status: 'running',
		updateTime: '',
		useTime: 0
	}) as NodeLastRunnerLog

const runnerNodesVersion = computed(() =>
	runnerStore.nodes
		.map((item) => `${item.nodeId}:${item.status}:${item.lastUpdateTime || ''}`)
		.join('|')
)

const runnerExecutionsVersion = computed(() =>
	runnerStore.executions
		.map((execution) =>
			execution.nodes
				.map(
					(item) =>
						`${execution.runnerKey}:${item.nodeId}:${item.status}:${item.lastUpdateTime || ''}`
				)
				.join(',')
		)
		.join('|')
)

const loadNodeLog = async (nodeId?: string) => {
	if (!nodeId) {
		latestRequestId += 1
		nodeLog.value = null
		errorMessage.value = ''
		loading.value = false
		return
	}

	const requestId = ++latestRequestId
	if (isCurrentNodeRunning(nodeId)) {
		nodeLog.value = createRunningNodeLog(nodeId)
		errorMessage.value = ''
		loading.value = false
		return
	}

	const runnerNodeState = findRunnerNodeState(nodeId)
	if (runnerNodeState) {
		nodeLog.value = toNodeLastRunnerLog(runnerNodeState)
		errorMessage.value = ''
		loading.value = false
		return
	}

	loading.value = true
	errorMessage.value = ''
	nodeLog.value = null

	try {
		const response = await agent.postAgentGetAgentNodeLastRunnerLogs({
			node_id: nodeId
		})

		if (requestId !== latestRequestId) return
		nodeLog.value = response?.result || null
	} catch (error) {
		if (requestId !== latestRequestId) return
		nodeLog.value = null
		errorMessage.value = error instanceof Error ? error.message : String(error)
	} finally {
		if (requestId === latestRequestId) {
			loading.value = false
		}
	}
}

watch(
	() =>
		[
			props.node?.id,
			props.active,
			runnerStore.currentRunnerKey,
			runnerStore.currentStartNodeId,
			runnerStore.status,
			runnerNodesVersion.value,
			runnerExecutionsVersion.value
		] as const,
	([nodeId, active]) => {
		if (!nodeId) {
			loadNodeLog()
			return
		}

		if (!active) return
		loadNodeLog(nodeId)
	},
	{ immediate: true }
)

const normalizedStatus = computed(() => {
	if (!nodeLog.value) return 'idle'

	const status = nodeLog.value.status?.toLowerCase()
	if (status === 'running' || status === 'connecting') return 'running'
	if (status === 'suspended') return 'suspended'
	if (status === 'failed' || status === 'error') return 'failed'
	if (status === 'success' || status === 'finished' || status === 'finish') return 'success'

	return nodeLog.value.raw?.is_success ? 'success' : 'idle'
})

const hasLog = computed(
	() =>
		!!nodeLog.value &&
		(!!nodeLog.value.raw ||
			!!nodeLog.value.updateTime ||
			!!nodeLog.value.creationTime ||
			normalizedStatus.value !== 'idle')
)

const statusText = computed(() => {
	switch (normalizedStatus.value) {
		case 'running':
			return t('pages.setter.nodeLog.running')
		case 'success':
			return t('pages.setter.nodeLog.success')
		case 'failed':
			return t('pages.setter.nodeLog.failed')
		case 'suspended':
			return t('pages.runWorkflow.suspended')
		default:
			return t('pages.setter.nodeLog.notRun')
	}
})

const statusType = computed<'info' | 'success' | 'warning' | 'danger'>(() => {
	switch (normalizedStatus.value) {
		case 'running':
			return 'warning'
		case 'success':
			return 'success'
		case 'failed':
			return 'danger'
		case 'suspended':
			return 'warning'
		default:
			return 'info'
	}
})

const inputData = computed(() => nodeLog.value?.raw?.input_variable || null)
const outputData = computed(() => nodeLog.value?.raw?.output_variable || null)
const lastUpdatedAt = computed(() => nodeLog.value?.updateTime || nodeLog.value?.creationTime || '')

const toPrettyJson = (value: unknown) => {
	if (value === null || value === undefined) return ''
	try {
		return typeof value === 'string' ? value : JSON.stringify(value, null, 2)
	} catch {
		return String(value)
	}
}

const prettyTrack = computed(() => {
	if (!nodeLog.value?.raw) return ''
	return toPrettyJson(nodeLog.value.raw)
})

const copyText = async (value: string) => {
	await navigator.clipboard.writeText(value || '')
	ElMessage.success(t('pages.setter.nodeLog.copySuccess'))
}
</script>

<template>
	<div v-loading="loading" class="node-log">
		<div v-if="!props.node" class="node-log__empty">{{ t('pages.setter.nodeLog.noNode') }}</div>
		<div v-else-if="errorMessage" class="node-log__empty">{{ errorMessage }}</div>
		<div v-else-if="!hasLog" class="node-log__empty">{{ t('pages.setter.nodeLog.noLog') }}</div>
		<div v-else class="node-log__content">
			<div class="node-log__header">
				<div class="node-log__title">
					<span class="node-log__name">{{
						props.node?.name || t('pages.setter.nodeLog.unnamedNode')
					}}</span>
					<el-tag :type="statusType" size="small" class="node-log__status-tag">
						{{ statusText }}
					</el-tag>
				</div>
				<div v-if="lastUpdatedAt" class="node-log__time">
					{{ t('pages.setter.nodeLog.lastUpdated') }}{{ lastUpdatedAt }}
				</div>
			</div>

			<div class="node-log__body">
				<div class="node-log__section-header">
					<div class="node-log__section-title">{{ t('pages.setter.nodeLog.input') }}</div>
					<el-button
						:icon="DocumentCopy"
						text
						size="small"
						class="node-log__copy-btn"
						@click="copyText(toPrettyJson(inputData))"
					>
						{{ t('pages.setter.nodeLog.copy') }}
					</el-button>
				</div>
				<pre class="node-log__pre">{{ toPrettyJson(inputData) }}</pre>
			</div>

			<div class="node-log__body">
				<div class="node-log__section-header">
					<div class="node-log__section-title">{{ t('pages.setter.nodeLog.output') }}</div>
					<el-button
						:icon="DocumentCopy"
						text
						size="small"
						class="node-log__copy-btn"
						@click="copyText(toPrettyJson(outputData))"
					>
						{{ t('pages.setter.nodeLog.copy') }}
					</el-button>
				</div>
				<pre class="node-log__pre">{{ toPrettyJson(outputData) }}</pre>
			</div>

			<div v-if="prettyTrack" class="node-log__body">
				<div class="node-log__section-header">
					<div class="node-log__section-title">{{ t('pages.setter.nodeLog.rawDetail') }}</div>
					<el-button
						:icon="DocumentCopy"
						text
						size="small"
						class="node-log__copy-btn"
						@click="copyText(prettyTrack)"
					>
						{{ t('pages.setter.nodeLog.copy') }}
					</el-button>
				</div>
				<pre class="node-log__pre">{{ prettyTrack }}</pre>
			</div>
		</div>
	</div>
</template>

<style lang="less" scoped>
.node-log {
	padding: 16px 20px;
	font-size: 13px;
	color: #111827;

	&__empty {
		color: #9ca3af;
		text-align: center;
		padding: 40px 0;
	}

	&__content {
		display: flex;
		flex-direction: column;
		gap: 12px;
	}

	&__header {
		display: flex;
		flex-direction: column;
		gap: 4px;
	}

	&__title {
		display: flex;
		align-items: center;
		gap: 8px;
		font-weight: 500;
	}

	&__name {
		color: #111827;
	}

	&__status-tag {
		border-radius: 999px;
	}

	&__time {
		font-size: 12px;
		color: #6b7280;
	}

	&__body {
		border-radius: 8px;
		border: 1px solid #e5e7eb;
		background: #f9fafb;
		padding: 12px;
	}

	&__section-header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 12px;
		margin-bottom: 8px;
	}

	&__section-title {
		font-size: 12px;
		color: #6b7280;
	}

	&__copy-btn {
		flex-shrink: 0;
		padding: 2px 4px;
		font-size: 12px;
		color: #6b7280;
	}

	&__pre {
		margin: 0;
		padding: 8px 10px;
		border-radius: 6px;
		background: #111827;
		color: #e5e7eb;
		font-family:
			ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
			monospace;
		font-size: 12px;
		line-height: 1.5;
		white-space: pre-wrap;
		word-break: break-all;
		max-height: 320px;
		min-height: 120px;
		overflow: auto;
	}
}
</style>
