<template>
	<view class="page">
		<scroll-view scroll-y class="page-scroll">
			<view class="hero-card">
				<view class="hero-kicker">AI EVENT PLANNING OUTLINE</view>
				<view class="hero-title">活动策划大纲生成</view>
				<view class="hero-desc">
					输入活动主题、受众、时间和资金等信息，系统会调用 `/ai/activityOutlineGen`
					生成结构化活动策划大纲，适合方案初稿、汇报材料和执行拆解场景。
				</view>
				<view class="hero-actions">
					<view class="hero-action" @tap="fillExample('volunteer')">志愿服务</view>
					<view class="hero-action" @tap="fillExample('salon')">主题沙龙</view>
					<view class="hero-action" @tap="fillExample('sports')">校园赛事</view>
				</view>
			</view>

			<view class="panel-card">
				<view class="panel-title">活动信息</view>
				<view class="field-list">
					<view class="field-item">
						<view class="field-label">活动主题</view>
						<uv-input
							v-model="form.event_theme"
							class="field-input"
							placeholder="例如：青春志愿行 绿美校园环保行动"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>
					<view class="field-item">
						<view class="field-label">面向受众</view>
						<uv-input
							v-model="form.people"
							class="field-input"
							placeholder="例如：全校团员青年、学生骨干、青年志愿者"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>
					<view class="field-item">
						<view class="field-label">活动时间</view>
						<uv-input
							v-model="form.event_time"
							class="field-input"
							placeholder="例如：2026年4月18日 14:30-17:30"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>
					<view class="field-item">
						<view class="field-label">活动资金</view>
						<uv-input
							v-model="form.event_funding"
							class="field-input"
							placeholder="例如：预算 5000 元，来源为校团委专项经费"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>
					<view class="field-item">
						<view class="field-label">活动形式</view>
						<uv-input
							v-model="form.event_type"
							class="field-input"
							placeholder="例如：专题讲座 + 分组互动 + 现场展示"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>
					<view class="field-item">
						<view class="field-label">活动地点</view>
						<uv-input
							v-model="form.event_location"
							class="field-input"
							placeholder="例如：大学生活动中心一楼报告厅"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>
					<view class="field-item">
						<view class="field-label">备注</view>
						<textarea
							v-model="form.event_meme"
							class="field-textarea"
							placeholder="补充活动背景、目标、关键限制、预期成果或希望体现的亮点"
							placeholder-style="color: #c0c4cc;"
							maxlength="-1"
						/>
					</view>
				</view>

				<view class="submit-bar">
					<view class="ghost-btn" @tap="resetForm">重置</view>
					<view class="primary-btn" :class="{ 'primary-btn--disabled': !canSubmit || loading }" @tap="handleGenerate">
						{{ loading ? '生成中' : '生成大纲' }}
					</view>
				</view>

				<view class="hint-block">
					<view class="hint-title">提交说明</view>
					<view class="hint-text">
						必填字段会直接作为 `inputs` 透传给 AI，后端固定触发“活动策划大纲生成”指令，并返回整理后的正文结果。
					</view>
				</view>
			</view>

			<view class="panel-card result-card">
				<view class="result-head">
					<view class="panel-title">生成结果</view>
					<view class="result-status" :class="loading ? 'result-status--loading' : hasOutlineContent ? 'result-status--done' : ''">
						{{ loading ? '生成中' : hasOutlineContent ? '已生成' : '待生成' }}
					</view>
				</view>

				<view v-if="loading" class="loading-panel">
					<view class="loading-kicker">OUTLINE GENERATING</view>
					<view class="loading-title">AI 正在整理活动策划结构</view>
					<view class="loading-desc">
						正在综合活动主题、受众、时间与经费信息，生成可直接二次编辑的策划大纲。
					</view>
				</view>

				<view v-if="showThinkingIndicator" class="outline-thinking">
					<view class="thinking-dot"></view>
					<text>AI 正在生成正文内容，请稍候...</text>
				</view>

				<view v-if="resultSummary" class="result-alert">
					{{ resultSummary }}
				</view>

				<view v-if="hasOutlineContent || loading" class="field-item">
					<view class="field-label">策划大纲</view>
					<textarea
						v-model="editableOutlineMarkdown"
						class="result-textarea"
						placeholder="填写左侧信息后开始生成活动策划大纲"
						placeholder-style="color: #c0c4cc;"
						maxlength="-1"
						:disabled="loading"
					/>
				</view>

				<view v-else class="result-empty">
					填写左侧信息后开始生成活动策划大纲
				</view>

				<view class="result-actions">
					<view class="result-btn" @tap="copyOutline">复制内容</view>
					<view class="result-btn" @tap="clearResult">清空结果</view>
				</view>
			</view>
		</scroll-view>
	</view>
</template>

<script>
	import {
		activityOutlineGen,
		buildWebSocketUrl,
		getCurrentUser
	} from './service'

	const STREAM_END_TEXT = '__END__'
	const SOCKET_CONNECTED_TEXT = '连接成功'
	const THINK_OPEN_TAG = '<think>'
	const THINK_CLOSE_TAG = '</think>'

	const createDefaultForm = () => ({
		event_theme: '',
		people: '',
		event_time: '',
		event_funding: '',
		event_type: '',
		event_location: '',
		event_meme: ''
	})

	export default {
		data() {
			return {
				form: createDefaultForm(),
				loading: false,
				result: null,
				outlineMarkdown: '',
				editableOutlineMarkdown: '',
				resultSummary: '',
				activeConversationId: '',
				activeMessageId: '',
				socketTask: null,
				socketConnectPromise: null,
				streamStarted: false,
				streamResolver: null,
				currentUser: {
					userId: null
				}
			}
		},
		computed: {
			canSubmit() {
				return !!(
					String(this.form.event_theme || '').trim() &&
					String(this.form.people || '').trim() &&
					String(this.form.event_time || '').trim() &&
					String(this.form.event_funding || '').trim()
				)
			},
			parsedOutlineContent() {
				return this.parseAssistantContent(this.outlineMarkdown)
			},
			visibleOutlineMarkdown() {
				return this.parsedOutlineContent.content
			},
			hasOutlineContent() {
				return !!String(this.editableOutlineMarkdown || '').trim()
			},
			showThinkingIndicator() {
				return this.loading && this.parsedOutlineContent.thinking
			}
		},
		onUnload() {
			this.clearStreamResolver()
			this.disconnectWebSocket()
		},
		methods: {
			showToast(title) {
				uni.showToast({
					title,
					icon: 'none'
				})
			},
			parseAssistantContent(value) {
				if (!value) {
					return {
						content: '',
						thinking: false
					}
				}

				let cursor = 0
				let content = ''
				let thinking = false

				while (cursor < value.length) {
					if (!thinking) {
						const openIndex = value.indexOf(THINK_OPEN_TAG, cursor)
						const closeIndex = value.indexOf(THINK_CLOSE_TAG, cursor)

						if (closeIndex !== -1 && (openIndex === -1 || closeIndex < openIndex)) {
							cursor = closeIndex + THINK_CLOSE_TAG.length
							continue
						}
						if (openIndex === -1) {
							content += value.slice(cursor)
							break
						}
						content += value.slice(cursor, openIndex)
						cursor = openIndex + THINK_OPEN_TAG.length
						thinking = true
						continue
					}

					const closeIndex = value.indexOf(THINK_CLOSE_TAG, cursor)
					if (closeIndex === -1) {
						break
					}
					cursor = closeIndex + THINK_CLOSE_TAG.length
					thinking = false
				}

				return {
					content,
					thinking
				}
			},
			syncEditableOutline() {
				this.editableOutlineMarkdown = this.visibleOutlineMarkdown
			},
			buildPayload() {
				return {
					event_theme: String(this.form.event_theme || '').trim(),
					people: String(this.form.people || '').trim(),
					event_time: String(this.form.event_time || '').trim(),
					event_funding: String(this.form.event_funding || '').trim(),
					event_type: String(this.form.event_type || '').trim(),
					event_location: String(this.form.event_location || '').trim(),
					event_meme: String(this.form.event_meme || '').trim()
				}
			},
			pickNonEmptyString() {
				for (let index = 0; index < arguments.length; index += 1) {
					const value = arguments[index]
					if (typeof value === 'string' && value.length > 0) {
						return value
					}
				}
				return ''
			},
			pickRawString() {
				for (let index = 0; index < arguments.length; index += 1) {
					const value = arguments[index]
					if (typeof value === 'string') {
						return value
					}
				}
				return undefined
			},
			stringifyUnknown(value) {
				try {
					return JSON.stringify(value, null, 2)
				} catch (error) {
					return String(value || '')
				}
			},
			parseOutline(payload) {
				if (!payload) {
					return ''
				}
				const answer = this.pickNonEmptyString(
					payload.answer,
					payload.raw && payload.raw.answer,
					payload.raw && payload.raw.data && payload.raw.data.answer,
					payload.raw && payload.raw.output,
					payload.raw && payload.raw.result,
					payload.raw && payload.raw.content,
					payload.raw && payload.raw.message
				)
				if (answer) {
					return answer
				}
				return this.stringifyUnknown((payload && payload.raw) || payload)
			},
			buildSummary() {
				const parts = []
				if (this.activeConversationId) {
					parts.push(`conversationId: ${this.activeConversationId}`)
				}
				if (this.activeMessageId) {
					parts.push(`messageId: ${this.activeMessageId}`)
				}
				return parts.join(' | ')
			},
			clearStreamResolver() {
				this.streamResolver = null
			},
			resolveStream() {
				if (this.streamResolver && this.streamResolver.resolve) {
					this.streamResolver.resolve()
				}
				this.clearStreamResolver()
			},
			rejectStream(error) {
				if (this.streamResolver && this.streamResolver.reject) {
					this.streamResolver.reject(error)
				}
				this.clearStreamResolver()
			},
			createStreamWaiter() {
				this.streamStarted = false
				return new Promise((resolve, reject) => {
					this.streamResolver = {
						resolve,
						reject
					}
				})
			},
			disconnectWebSocket() {
				this.socketConnectPromise = null
				const socketTask = this.socketTask
				this.socketTask = null
				if (socketTask && typeof socketTask.close === 'function') {
					try {
						socketTask.close({})
					} catch (error) {
						console.warn('socket close failed', error)
					}
				}
			},
			appendOutlineChunk(chunk, conversationId, messageId) {
				if (!chunk) {
					return
				}
				if (conversationId && conversationId !== '0') {
					this.activeConversationId = conversationId
				}
				if (messageId && messageId !== '0') {
					this.activeMessageId = messageId
				}
				if (!this.streamStarted) {
					this.streamStarted = true
					this.outlineMarkdown = ''
				}
				this.outlineMarkdown += chunk
				this.syncEditableOutline()
				this.resultSummary = this.buildSummary() || '正在通过 WebSocket 接收流式内容'
			},
			finishStream(conversationId, messageId) {
				if (conversationId && conversationId !== '0') {
					this.activeConversationId = conversationId
				}
				if (messageId && messageId !== '0') {
					this.activeMessageId = messageId
				}
				this.resultSummary = this.buildSummary() || '已通过 WebSocket 接收生成结果'
				this.loading = false
				this.resolveStream()
				this.disconnectWebSocket()
			},
			handleSocketMessage(raw) {
				if (!raw) {
					return
				}
				try {
					const data = JSON.parse(raw)
					if (data && data.answer === STREAM_END_TEXT) {
						this.finishStream(data.conversationId, data.messageId)
						return
					}
					if (data && (data.loading === 'true' || data.loading === true)) {
						this.resultSummary = 'AI 正在组织活动策划大纲结构...'
						return
					}
					const chunk = this.pickRawString(data && data.notice, data && data.answer)
					if (typeof chunk === 'string') {
						this.appendOutlineChunk(chunk, data && data.conversationId, data && data.messageId)
					}
				} catch (error) {
					this.appendOutlineChunk(raw)
				}
			},
			refreshUser() {
				this.currentUser = getCurrentUser()
				return this.currentUser
			},
			connectWebSocket() {
				const user = this.refreshUser()
				if (!user.userId) {
					return Promise.reject(new Error('未获取到当前登录用户'))
				}
				const currentSocket = this.socketTask
				if (currentSocket && this.socketConnectPromise) {
					return this.socketConnectPromise
				}
				if (currentSocket) {
					this.disconnectWebSocket()
				}

				let cleanupPending = () => undefined
				const connectPromise = new Promise((resolve, reject) => {
					let settled = false
					const socketTask = uni.connectSocket({
						url: buildWebSocketUrl(user.userId),
						complete: () => {}
					})

					cleanupPending = () => {
						if (this.socketConnectPromise === connectPromise) {
							this.socketConnectPromise = null
						}
					}

					socketTask.onMessage((event) => {
						const raw = String((event && event.data) || '')
						if (raw === SOCKET_CONNECTED_TEXT) {
							if (!settled) {
								settled = true
								cleanupPending()
								resolve()
							}
							return
						}
						this.handleSocketMessage(raw)
					})
					socketTask.onClose(() => {
						if (this.socketTask === socketTask) {
							this.socketTask = null
						}
						if (!settled) {
							settled = true
							cleanupPending()
							reject(new Error('活动策划大纲连接已关闭'))
							return
						}
						if (this.loading) {
							this.rejectStream(new Error('活动策划大纲连接已关闭'))
							this.loading = false
						}
						cleanupPending()
					})
					socketTask.onError(() => {
						if (!settled) {
							settled = true
							cleanupPending()
							reject(new Error('活动策划大纲连接失败'))
							return
						}
						if (this.loading) {
							this.rejectStream(new Error('活动策划大纲连接失败'))
							this.loading = false
						}
					})
					this.socketTask = socketTask
				})
				this.socketConnectPromise = connectPromise
				return connectPromise
			},
			clearResult() {
				this.result = null
				this.outlineMarkdown = ''
				this.editableOutlineMarkdown = ''
				this.resultSummary = ''
				this.activeConversationId = ''
				this.activeMessageId = ''
			},
			resetForm() {
				this.form = createDefaultForm()
				this.clearResult()
			},
			async handleGenerate() {
				if (this.loading) {
					return
				}
				if (!this.canSubmit) {
					this.showToast('请先填写全部必填项')
					return
				}
				this.loading = true
				this.clearResult()
				this.resultSummary = '正在建立 WebSocket 连接...'
				try {
					await this.connectWebSocket()
					this.resultSummary = '连接成功，已提交生成任务，等待流式返回...'
					const waitStream = this.createStreamWaiter()
					const data = await activityOutlineGen(this.buildPayload())
					this.result = data || null
					if (!this.streamStarted && data && this.parseOutline(data)) {
						this.outlineMarkdown = this.parseOutline(data)
						this.syncEditableOutline()
						this.resultSummary = this.buildSummary() || '接口直接返回生成结果'
						this.loading = false
						this.resolveStream()
						this.disconnectWebSocket()
					} else {
						await waitStream
					}
					if (!this.visibleOutlineMarkdown) {
						this.showToast('接口已返回，但未解析到正文内容')
						return
					}
					this.showToast('活动策划大纲生成完成')
				} catch (error) {
					this.disconnectWebSocket()
					this.clearStreamResolver()
					this.loading = false
					this.showToast((error && error.message) || '生成失败')
				}
			},
			copyOutline() {
				if (!this.editableOutlineMarkdown) {
					this.showToast('暂无可复制内容')
					return
				}
				uni.setClipboardData({
					data: this.editableOutlineMarkdown,
					success: () => {
						this.showToast('内容已复制')
					}
				})
			},
			fillExample(type) {
				if (type === 'volunteer') {
					this.form.event_theme = '青春志愿行 绿美校园环保行动'
					this.form.people = '全校团员青年、学生骨干、青年志愿者'
					this.form.event_time = '2026年4月18日 14:30-17:30'
					this.form.event_funding = '预算 5000 元，来源为校团委专项经费'
					this.form.event_type = '志愿服务 + 分组清洁 + 环保倡议'
					this.form.event_location = '校园主干道、教学楼周边和青年林'
					this.form.event_meme = '突出劳动教育、志愿服务精神和绿色校园建设，生成含活动背景、流程、分工、宣传和风险预案的大纲。'
					return
				}
				if (type === 'salon') {
					this.form.event_theme = '青春思享汇 新时代青年成长主题沙龙'
					this.form.people = '学生干部、团支部书记、青年学生代表'
					this.form.event_time = '2026年5月10日 19:00-21:00'
					this.form.event_funding = '预算 3000 元，包含物料、海报和场地布置'
					this.form.event_type = '主题分享 + 圆桌讨论 + 互动提问'
					this.form.event_location = '大学生活动中心多功能厅'
					this.form.event_meme = '希望突出思想引领、青年交流和成果展示，生成适合汇报审批的结构化活动方案。'
					return
				}
				this.form.event_theme = '青春杯校园三人篮球赛'
				this.form.people = '各学院学生代表队、体育类社团、观赛学生'
				this.form.event_time = '2026年4月下旬周末全天'
				this.form.event_funding = '预算 8000 元，含裁判、奖品、宣传和后勤保障'
				this.form.event_type = '校园赛事 + 开幕仪式 + 分组淘汰赛'
				this.form.event_location = '学校室外篮球场'
				this.form.event_meme = '需要体现赛事组织、赛程安排、安全保障、医疗预案和宣传报道计划。'
			}
		}
	}
</script>

<style lang="scss" scoped>
	.page {
		min-height: 100vh;
		background:
			radial-gradient(circle at top right, rgba(51, 97, 164, 0.14), transparent 26%),
			linear-gradient(180deg, #f7f9fc 0%, #f2f4f8 100%);
	}

	.page-scroll {
		height: 100vh;
		padding: 28rpx 24rpx 40rpx;
	}

	.hero-card {
		padding: 34rpx 30rpx;
		border-radius: 32rpx;
		background: linear-gradient(135deg, #16315f 0%, #26508e 52%, #4b79bb 100%);
		box-shadow: 0 18rpx 44rpx rgba(33, 67, 121, 0.18);
		color: #eef5ff;
	}

	.hero-kicker {
		font-size: 22rpx;
		letter-spacing: 4rpx;
		color: rgba(238, 245, 255, 0.72);
	}

	.hero-title {
		margin-top: 18rpx;
		font-size: 42rpx;
		line-height: 1.35;
		font-weight: 700;
	}

	.hero-desc {
		margin-top: 16rpx;
		font-size: 24rpx;
		line-height: 1.8;
		color: rgba(238, 245, 255, 0.88);
	}

	.hero-actions {
		display: flex;
		flex-wrap: wrap;
		gap: 14rpx;
		margin-top: 26rpx;
	}

	.hero-action {
		padding: 12rpx 20rpx;
		border-radius: 999rpx;
		background: rgba(255, 255, 255, 0.12);
		font-size: 22rpx;
		color: #f4f8ff;
	}

	.panel-card {
		margin-top: 24rpx;
		padding: 26rpx;
		border-radius: 30rpx;
		background: rgba(255, 255, 255, 0.96);
		border: 1rpx solid rgba(51, 97, 164, 0.08);
		box-shadow: 0 10rpx 28rpx rgba(61, 73, 95, 0.08);
	}

	.panel-title {
		font-size: 30rpx;
		font-weight: 700;
		color: #1f2329;
	}

	.field-list {
		display: flex;
		flex-direction: column;
		gap: 22rpx;
		margin-top: 20rpx;
	}

	.field-item {
		width: 100%;
	}

	.field-label {
		margin-bottom: 12rpx;
		font-size: 25rpx;
		font-weight: 600;
		color: #2b3037;
	}

	.field-textarea {
		width: 100%;
		min-height: 220rpx;
		padding: 22rpx 24rpx;
		border-radius: 22rpx;
		background: #f6f8fb;
		border: 1rpx solid #dde4ef;
		font-size: 24rpx;
		line-height: 1.8;
		color: #2a2e35;
		box-sizing: border-box;
	}

	:deep(.field-input.uv-input) {
		width: 100%;
		min-height: 88rpx;
		padding: 0 24rpx;
		border-radius: 12rpx;
		border: 1px solid #dcdfe6;
		background: #ffffff;
		box-sizing: border-box;
	}

	:deep(.field-input.uv-input .uv-input__content__field-wrapper__field) {
		height: 88rpx;
		min-height: 88rpx;
		font-size: 28rpx;
		line-height: 88rpx;
		color: #303133;
	}

	.submit-bar,
	.result-actions {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 18rpx;
		margin-top: 24rpx;
	}

	.ghost-btn,
	.primary-btn,
	.result-btn {
		height: 88rpx;
		line-height: 88rpx;
		border-radius: 22rpx;
		font-size: 28rpx;
		font-weight: 700;
		text-align: center;
	}

	.ghost-btn {
		background: #f6f7fa;
		color: #50617a;
		border: 1rpx solid #d8dfeb;
	}

	.primary-btn {
		background: linear-gradient(135deg, #1f4f8f 0%, #3f70b5 100%);
		color: #fff;
	}

	.primary-btn--disabled {
		opacity: 0.6;
	}

	.result-btn {
		background: #eef4fb;
		color: #24508f;
	}

	.hint-block,
	.result-alert {
		margin-top: 22rpx;
		padding: 22rpx 24rpx;
		border-radius: 24rpx;
		background: #f5f8fd;
		border: 1rpx solid rgba(51, 97, 164, 0.08);
		font-size: 22rpx;
		line-height: 1.75;
		color: #6d7d93;
	}

	.hint-title {
		font-size: 24rpx;
		font-weight: 700;
		color: #35527d;
	}

	.hint-text {
		margin-top: 10rpx;
	}

	.result-card {
		margin-bottom: 20rpx;
	}

	.result-head {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16rpx;
	}

	.result-status {
		padding: 10rpx 16rpx;
		border-radius: 999rpx;
		background: #eef1f5;
		color: #7e8792;
		font-size: 20rpx;
		font-weight: 600;
	}

	.result-status--loading {
		background: #edf5ff;
		color: #1554ad;
	}

	.result-status--done {
		background: #eef9f1;
		color: #1f8f49;
	}

	.loading-panel {
		margin-top: 22rpx;
		padding: 26rpx;
		border-radius: 26rpx;
		background: linear-gradient(135deg, #f5f9ff 0%, #eef4fb 100%);
	}

	.loading-kicker {
		font-size: 20rpx;
		letter-spacing: 3rpx;
		color: #6682aa;
	}

	.loading-title {
		margin-top: 10rpx;
		font-size: 30rpx;
		font-weight: 700;
		color: #23446f;
	}

	.loading-desc {
		margin-top: 12rpx;
		font-size: 22rpx;
		line-height: 1.75;
		color: #6d7d93;
	}

	.outline-thinking {
		display: flex;
		align-items: center;
		gap: 12rpx;
		margin-top: 20rpx;
		font-size: 22rpx;
		color: #35527d;
	}

	.thinking-dot {
		width: 16rpx;
		height: 16rpx;
		border-radius: 50%;
		background: #4b79bb;
	}

	.result-textarea {
		width: 100%;
		min-height: 560rpx;
		padding: 24rpx;
		border-radius: 24rpx;
		background: #f8fafc;
		border: 1rpx solid #e1e7f0;
		font-size: 24rpx;
		line-height: 1.85;
		color: #2b3037;
		box-sizing: border-box;
	}

	.result-empty {
		margin-top: 22rpx;
		padding: 32rpx 24rpx;
		border-radius: 24rpx;
		background: #f8fafc;
		font-size: 24rpx;
		line-height: 1.75;
		color: #8a94a0;
		text-align: center;
	}
</style>
