<template>
  <view class="script-view">
    <view class="cosmic-background">
      <view class="cosmic-stars layer-one"></view>
      <view class="cosmic-stars layer-two"></view>
      <view class="cosmic-planet planet-main"></view>
      <view class="cosmic-planet planet-soft"></view>
      <view class="meteor meteor-one"></view>
      <view class="meteor meteor-two"></view>
      <view class="meteor meteor-three"></view>
      <view class="meteor meteor-four"></view>
      <view class="meteor meteor-five"></view>
    </view>
    <view v-if="viewState === 'home'" class="wish-home">
      <view class="home-head">
        <view class="history-button" @click="openScriptLibrary">
          <view class="history-lines">
            <view></view>
            <view></view>
            <view></view>
          </view>
          <text>历史</text>
        </view>
      </view>

      <view class="hero-copy">
        <view class="hero-title-new">
          <view class="hero-title-line">
            <text>今天有什么</text>
            <text class="hero-highlight">心愿</text>
          </view>
          <text class="hero-title-line hero-title-tail">想实现</text>
        </view>
        <text class="hero-title">今天有什么<text class="hero-highlight">心愿</text>想实现</text>
      </view>

      <view class="inspiration-section">
        <view class="section-line">
          <text class="section-title">灵感一下</text>
          <text class="refresh" @click="shuffleInspirations">换一换</text>
        </view>
        <view class="recommend-grid">
          <view
            v-for="item in recommendations"
            :key="item.text"
            class="recommend-card"
            @click="useRecommendation(item.text)"
          >
            <text class="recommend-text">{{ item.text }}</text>
          </view>
        </view>
      </view>

      <view class="orb-wrap">
        <view
          class="mic-orb"
          :class="{ pressing: voiceState === 'pressing', recognizing: voiceState === 'recognizing' }"
          @touchstart.prevent="startVoicePress"
          @touchend.prevent="endVoicePress"
          @touchcancel.prevent="cancelVoicePress"
        >
          <view class="mic-symbol">
            <view class="mic-head"></view>
            <view class="mic-stem"></view>
            <view class="mic-base"></view>
          </view>
        </view>
      </view>

      <text class="voice-copy">{{ voiceCopy }}</text>

      <view class="wish-input-wrap" :class="{ active: homeInputFocused, twoLine: homeInputLevel === 'two', expanded: homeInputLevel === 'multi' }">
        <text v-if="!wishText" class="custom-input-placeholder home-input-placeholder">写下你的心愿，AI帮你重写人生</text>
        <textarea
          class="wish-input"
          v-model="wishText"
          :auto-height="homeInputLevel !== 'single'"
          :show-confirm-bar="false"
          confirm-type="send"
          maxlength="500"
          @focus="homeInputFocused = true"
          @blur="homeInputFocused = false"
          @confirm="submitWish('text')"
        />
        <view class="send-button" :class="{ disabled: !wishText.trim() }" @click="submitWish('text')">发送</view>
      </view>
    </view>

    <view v-else-if="viewState === 'generating'" class="generation-view">
      <scroll-view
        class="generation-scroll"
        scroll-y
        :scroll-top="generationScrollTop"
        :scroll-into-view="generationScrollTarget"
        :scroll-with-animation="true"
        :enhanced="true"
        :show-scrollbar="false"
      >
        <view class="generation-scroll-content">
      <view class="conversation">
        <view class="chat-bubble user">
          <text>{{ generationDisplayText }}</text>
          <text class="bubble-time">{{ currentMessageTime }}</text>
        </view>
        <view class="chat-bubble system">
          <text>{{ generationTitle }}</text>
          <view v-if="showThinkingDots" class="thinking-dots">
            <view></view>
            <view></view>
            <view></view>
          </view>
          <text v-if="generationStatus === 'failed'" class="generation-error">{{ generationFailureCopy }}</text>
          <text v-else-if="visibleStreamContent" class="stream-preview typing">{{ visibleStreamContent }}<text v-if="generating" class="typing-cursor">|</text></text>
          <text class="bubble-time">{{ currentMessageTime }}</text>
        </view>
      </view>
      <view class="generation-loading">
      <view class="loading-orbit" :class="{ streaming: generationStatus === 'streaming', failed: generationStatus === 'failed' }">
        <view class="orbit-ring outer"></view>
        <view class="orbit-ring inner"></view>
        <view class="orbit-core"></view>
      </view>
      <text class="loading-copy">{{ generationCopy }}</text>
      <text v-if="generationStatus !== 'failed'" class="loading-subcopy">{{ generationSubcopy }}</text>
      <view v-else class="generation-actions">
        <button class="generation-action primary" @click="retryGeneration">再试一次</button>
        <button class="generation-action secondary" @click="returnToEdit">返回修改</button>
      </view>
      </view>
          <view :id="generationScrollAnchor" class="generation-scroll-anchor"></view>
        </view>
      </scroll-view>
    </view>

    <view v-else class="result-page">
      <view class="result-top-actions">
        <view class="history-button" @click="openScriptLibrary">
          <view class="history-lines">
            <view></view>
            <view></view>
            <view></view>
          </view>
          <text>历史</text>
        </view>
        <button class="page-close-btn" @click="closeResult">×</button>
      </view>

      <scroll-view
        class="result-view"
        scroll-y
        :scroll-top="resultCommandScrollTop"
        :scroll-into-view="resultScrollTarget"
        :scroll-with-animation="true"
        :enhanced="true"
        :show-scrollbar="false"
        @scroll="handleResultScroll"
      >
        <view class="result-scroll-content">
          <view id="result-scroll-top" class="result-scroll-top"></view>

          <view class="conversation compact">
            <view class="chat-bubble user">
              <text>{{ wishText }}</text>
              <text class="bubble-time">{{ currentMessageTime }}</text>
            </view>
            <view class="chat-bubble system done">
              <text>心愿已实现，故事已为你展开</text>
              <text class="bubble-time">{{ currentResultTime }}</text>
            </view>
          </view>

          <view class="story-card" :class="{ collapsed: storyCollapsed, dialogMode: resultHasScriptReplies }">
            <view class="story-head">
              <view class="story-title-wrap">
                <text class="story-title">{{ currentResult?.title || '我的人生剧本' }}</text>
                <view class="tag-row">
                  <text v-for="tag in resultTags" :key="tag" class="tag">{{ tag }}</text>
                </view>
              </view>
              <view class="story-head-actions">
                <button class="collapse-icon" @click="toggleStoryCollapse">
                  <text class="collapse-icon-text">{{ storyCollapsed ? '展开' : '收起' }}</text>
                  <view class="collapse-chevron" :class="{ down: storyCollapsed }">
                    <view></view>
                    <view></view>
                  </view>
                </button>
                <button class="copy-card-btn" @click="copyResultContent">复制</button>
              </view>
            </view>

            <template v-if="!resultHasScriptReplies">
              <scroll-view
                v-if="storyCollapsed"
                class="story-body-scroll"
                scroll-y
                :enhanced="true"
                :show-scrollbar="true"
              >
                <text class="story-body" :selectable="true" :user-select="true">{{ displayedResultContent }}</text>
              </scroll-view>
              <text v-else class="story-body" :selectable="true" :user-select="true">{{ displayedResultContent }}</text>
              <view class="collapse-row" @click="toggleStoryCollapse">
                <text class="collapse-row-text">{{ storyCollapsed ? '展开全文' : '收起全文' }}</text>
                <view class="collapse-chevron" :class="{ down: storyCollapsed }">
                  <view></view>
                  <view></view>
                </view>
              </view>
            </template>

            <view class="result-actions">
              <button class="action-btn" @click="changeDirection">换个方向</button>
              <button class="action-btn" @click="notLikeMe">不像我</button>
              <button class="action-btn" @click="continueInChat">继续生成</button>
              <button class="action-btn primary" @click="trackTtsClick">
                <text class="action-icon">{{ ttsActionIcon }}</text>
                <text>{{ ttsActionText }}</text>
              </button>
            </view>
          </view>

          <view v-if="resultMessages.length" class="result-chat-list">
            <view
              v-for="message in resultMessages"
              :key="message.id"
              class="chat-bubble"
              :class="{ user: message.role === 'user', system: message.role === 'assistant', pending: message.pending }"
            >
              <text selectable user-select>{{ getMessageDisplayContent(message) }}</text>
              <view v-if="message.pending" class="thinking-dots">
                <view></view>
                <view></view>
                <view></view>
              </view>
              <view
                v-if="isAssistantMessage(message)"
                class="message-toggle"
                @click.stop="toggleMessageCollapse(message)"
              >
                <text>{{ isMessageCollapsed(message) ? '展开' : '收起' }}</text>
                <text class="message-toggle-icon" :class="{ open: !isMessageCollapsed(message) }">⌃</text>
              </view>
              <text class="bubble-time">{{ message.time }}</text>
            </view>
          </view>

          <view v-if="resultChatting" class="generation-loading">
            <view class="loading-orbit streaming">
              <view class="orbit-ring outer"></view>
              <view class="orbit-ring inner"></view>
              <view class="orbit-core"></view>
            </view>
            <text class="loading-copy">正在为你重写人生</text>
            <text class="loading-subcopy">AI 正在思考下一步方向</text>
          </view>

          <view :id="resultScrollAnchor" class="result-scroll-anchor"></view>
        </view>
      </scroll-view>

      <view v-if="showScrollTopBtn || showScrollBottomBtn" class="scroll-jump-group">
        <view v-if="showScrollTopBtn" class="scroll-jump-btn" @click="scrollResultToTop">顶部</view>
        <view v-if="showScrollBottomBtn" class="scroll-jump-btn" @click="scrollResultToBottom">底部</view>
      </view>

      <view class="result-chat-bar">
        <view
          class="chat-voice-btn"
          :class="{ pressing: voiceState === 'pressing', recognizing: voiceState === 'recognizing' }"
          @touchstart.prevent="startVoicePress"
          @touchend.prevent="endVoicePress"
          @touchcancel.prevent="cancelVoicePress"
        >
          <text>语音</text>
        </view>
        <view class="result-input-shell" :class="{ twoLine: resultInputLevel === 'two', expanded: resultInputLevel === 'multi' }">
          <text v-if="!resultChatInput" class="custom-input-placeholder result-input-placeholder">继续提修改建议，或确认后重新生成</text>
          <textarea
            class="result-chat-input"
            v-model="resultChatInput"
            :auto-height="resultInputLevel !== 'single'"
            :show-confirm-bar="false"
            :focus="resultInputFocus"
            maxlength="500"
            confirm-type="send"
            @focus="resultInputFocused = true"
            @blur="resultInputFocused = false"
            @confirm="sendResultChat('text')"
          />
        </view>
        <view class="chat-send-btn" :class="{ disabled: !resultChatInput.trim() || resultChatting }" @click="sendResultChat('text')">发送</view>
      </view>
    </view>
  </view>
</template>

<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useAppStore } from '../../stores/app.js'
import analytics from '../../services/analytics.js'
import * as epicScriptService from '../../services/epicScript.js'
import { useTtsPlayer } from '../../composables/useTtsPlayer.js'
import { useTypewriterStream } from '../../composables/useTypewriterStream.js'
import { transcribeAudio } from '../../services/asr.js'
import { streamAiScene } from '../../services/aiRuntime.js'

const store = useAppStore()
const pagePath = '/pages/main/ScriptView'
const viewState = ref('home')
const wishText = ref('')
const voiceState = ref('idle')
const generationStartedAt = ref(0)
const currentResult = ref(null)
const currentMessageTime = ref('')
const currentResultTime = ref('')
const streamContent = ref('')
const generating = ref(false)
const streamWriter = useTypewriterStream({ interval: 32, step: 1 })
const resultChatWriter = useTypewriterStream({ interval: 32, step: 1 })
const generationStatus = ref('idle')
const generationError = ref('')
const generationHintIndex = ref(0)
const generationScrollAnchor = ref('generation-stream-anchor-a')
const generationScrollTarget = ref('')
const generationScrollTop = ref(0)
const generationAutoFollow = ref(true)
const generationDisplayText = ref('')
const lastGenerationPrompt = ref('')
const lastSubmitSource = ref('text')
const lastGenerationRevision = ref(false)
const homeInputFocused = ref(false)
const resultInputFocused = ref(false)
const resultInputFocus = ref(false)
const storyCollapsed = ref(false)
const resultChatInput = ref('')
const resultMessages = ref([])
const collapsedMessageIds = ref({})
const resultChatting = ref(false)
const resultScrollAnchor = ref('result-scroll-anchor-a')
const resultScrollTarget = ref('')
const resultScrollTop = ref(0)
const resultCommandScrollTop = ref(0)
const resultScrollHeight = ref(0)
const resultViewportHeight = ref(0)
const activeAssistantMessageId = ref('')
const revisionIntent = ref('')
const currentConversationId = ref('')
const remainingCount = ref(3)
const style = ref('career')
const randomRecommendations = ref([])
const useSocialInsights = ref(true)
const ttsPlayer = useTtsPlayer({ pagePath })
let recorderManager = null
let recordStartedAt = 0
let recordCancelled = false
let generationHintTimer = null
let generationSlowTimer = null
let generationVerySlowTimer = null
let generationScrollTimer = null
let resultScrollTargetTimer = null
let pendingOpenTimer = null

const generationHints = [
  '正在从你的心愿里寻找故事的起点',
  '正在整理人生素材和情绪线索',
  '正在安排一次更爽的命运转弯',
  '正在让故事慢慢靠近你'
]

watch(() => currentResult.value?.id, (sourceId, previousSourceId) => {
  if (sourceId && sourceId !== previousSourceId) {
    ttsPlayer.prewarmSource(sourceId)
  }
})

const fallbackRecommendations = [
  { text: '如果老板今天突然夸我，我的人生会怎样展开？', tag: '职场逆袭' },
  { text: '我不再内耗，专注搞钱，逆袭成行业顶尖', tag: '成长' },
  { text: '重生回18岁，这次我要活成自己喜欢的样子', tag: '重生' },
  { text: '我终于被所有人看见，也被自己认可', tag: '被认可' }
]

const recommendations = computed(() => {
  const source = randomRecommendations.value.length ? randomRecommendations.value : (store.inspirationRecommendations || [])
  return source.length ? source.slice(0, 4) : fallbackRecommendations
})

const voiceCopy = computed(() => {
  if (voiceState.value === 'pressing') return '松开后识别心愿'
  if (voiceState.value === 'recognizing') return '正在识别你的心愿……'
  if (voiceState.value === 'error') return '语音暂不可用，可以先输入文字'
  return '按住说话，即刻如愿'
})

const resultTags = computed(() => {
  const tags = currentResult.value?.tags
  if (Array.isArray(tags) && tags.length) return tags.slice(0, 3)
  const styleText = currentResult.value?.style || '爽文'
  return [styleText, '成长', '被看见']
})

const resultContent = computed(() => {
  return currentResult.value?.content || currentResult.value?.summary || '故事正在生成，请稍后查看。'
})

const getInputLevel = (text, singleThreshold = 18, twoLineThreshold = 42) => {
  const value = String(text || '')
  const trimmedLength = value.trim().length
  const explicitLines = value.split('\n').length
  if (!trimmedLength) return 'single'
  if (explicitLines >= 3 || trimmedLength > twoLineThreshold) return 'multi'
  if (explicitLines === 2 || trimmedLength > singleThreshold) return 'two'
  return 'single'
}

const homeInputLevel = computed(() => getInputLevel(wishText.value, 20, 46))
const resultInputLevel = computed(() => getInputLevel(resultChatInput.value, 18, 42))

const displayedResultContent = computed(() => {
  return resultContent.value || ''
})

const ASSISTANT_MESSAGE_PREVIEW_LENGTH = 120

const isAssistantMessage = (message) => {
  return message?.role === 'assistant' && !message.pending
}

const isMessageCollapsed = (message) => {
  if (!isAssistantMessage(message)) return false
  return Boolean(collapsedMessageIds.value[message.id])
}

const getMessageDisplayContent = (message) => {
  const content = String(message?.content || '')
  if (!isMessageCollapsed(message)) return content
  if (content.length <= ASSISTANT_MESSAGE_PREVIEW_LENGTH) return content
  return `${content.slice(0, ASSISTANT_MESSAGE_PREVIEW_LENGTH)}...`
}

const toggleMessageCollapse = (message) => {
  if (!isAssistantMessage(message)) return
  collapsedMessageIds.value = {
    ...collapsedMessageIds.value,
    [message.id]: !isMessageCollapsed(message)
  }
  nextTick(measureResultViewport)
}

const visibleStreamContent = computed(() => streamWriter.visibleText.value)
const resultCanScroll = computed(() => resultScrollHeight.value > resultViewportHeight.value + 24)
const resultAtTop = computed(() => resultScrollTop.value <= 24)
const resultAtBottom = computed(() => {
  if (!resultCanScroll.value) return true
  return resultScrollTop.value + resultViewportHeight.value >= resultScrollHeight.value - 36
})
const showScrollTopBtn = computed(() => resultCanScroll.value && !resultAtTop.value)
const showScrollBottomBtn = computed(() => resultCanScroll.value && !resultAtBottom.value)
const resultHasScriptReplies = computed(() => {
  return resultMessages.value.some(item => item.role === 'assistant' && item.kind === 'script')
})

const measureResultViewport = () => {
  nextTick(() => {
    const query = uni.createSelectorQuery()
    query.select('.result-view').boundingClientRect()
    query.select('.result-scroll-content').boundingClientRect()
    query.exec((rects = []) => {
      const viewport = rects[0]
      const content = rects[1]
      if (viewport?.height) resultViewportHeight.value = viewport.height
      if (content?.height) resultScrollHeight.value = content.height
    })
  })
}

const handleResultScroll = (event) => {
  const detail = event?.detail || {}
  resultScrollTop.value = Number(detail.scrollTop || 0)
  resultScrollHeight.value = Number(detail.scrollHeight || resultScrollHeight.value || 0)
  if (!resultViewportHeight.value) measureResultViewport()
}

const setResultScrollTarget = (target) => {
  if (!target) return
  if (resultScrollTargetTimer) {
    clearTimeout(resultScrollTargetTimer)
    resultScrollTargetTimer = null
  }
  resultScrollTarget.value = ''
  nextTick(() => {
    resultScrollTarget.value = target
    resultScrollTargetTimer = setTimeout(() => {
      if (resultScrollTarget.value === target) resultScrollTarget.value = ''
      resultScrollTargetTimer = null
    }, 480)
  })
}

const pushResultScrollCommand = (top) => {
  const nextTop = Number(top || 0)
  resultCommandScrollTop.value = resultCommandScrollTop.value === nextTop
    ? nextTop + 1
    : nextTop
}

const scrollGenerationToLatest = () => {
  if (viewState.value !== 'generating') return
  if (!generationAutoFollow.value) return
  if (generationScrollTimer) return
  generationScrollTimer = setTimeout(() => {
    generationScrollTimer = null
    if (viewState.value !== 'generating' || !generationAutoFollow.value) return
    nextTick(() => {
      generationScrollTop.value = generationScrollTop.value >= 100000000
        ? 100000
        : generationScrollTop.value + 100000
      generationScrollTarget.value = ''
      generationScrollAnchor.value = generationScrollAnchor.value === 'generation-stream-anchor-a'
        ? 'generation-stream-anchor-b'
        : 'generation-stream-anchor-a'
      nextTick(() => {
        generationScrollTarget.value = generationScrollAnchor.value
      })
    })
  }, 48)
}

const keepGenerationAtBottom = () => {
  ;[0, 60, 140, 260, 420].forEach((delay) => {
    setTimeout(scrollGenerationToLatest, delay)
  })
}

const scrollResultToLatest = () => {
  if (viewState.value !== 'result') return
  nextTick(() => {
    measureResultViewport()
    pushResultScrollCommand(1000000)
    resultScrollAnchor.value = resultScrollAnchor.value === 'result-scroll-anchor-a'
      ? 'result-scroll-anchor-b'
      : 'result-scroll-anchor-a'
    setResultScrollTarget(resultScrollAnchor.value)
  })
}

const scrollResultToBottom = () => {
  resultScrollTop.value = Math.max(0, resultScrollHeight.value - resultViewportHeight.value)
  scrollResultToLatest()
}

const keepResultAtBottom = () => {
  ;[0, 80, 180, 360, 720].forEach((delay) => {
    setTimeout(scrollResultToLatest, delay)
  })
}

const scrollResultToTop = () => {
  if (viewState.value !== 'result') return
  resultScrollTop.value = 0
  resultCommandScrollTop.value = 1
  nextTick(() => {
    resultCommandScrollTop.value = 0
    setResultScrollTarget('result-scroll-top')
  })
}

watch(visibleStreamContent, (text) => {
  if (!text) return
  scrollGenerationToLatest()
})

watch(generationStatus, () => {
  scrollGenerationToLatest()
})

watch(viewState, (state) => {
  if (state !== 'result') return
  measureResultViewport()
})

watch(resultChatWriter.visibleText, (text) => {
  if (!activeAssistantMessageId.value) return
  const message = resultMessages.value.find(item => item.id === activeAssistantMessageId.value)
  if (!message) return
  message.content = text
  keepResultAtBottom()
})

const generationTitle = computed(() => {
  if (generationStatus.value === 'failed') return '心愿暂时没有抵达'
  if (visibleStreamContent.value) return '故事正在展开'
  return '心愿实现中……'
})

const showThinkingDots = computed(() => {
  return generationStatus.value !== 'failed' && !visibleStreamContent.value
})

const generationCopy = computed(() => {
  if (generationStatus.value === 'failed') return '这次生成没有顺利完成'
  if (generationStatus.value === 'verySlow') return '这次需要久一点，仍在努力连接灵感'
  if (generationStatus.value === 'slow') return '还在整理，请再给我一点时间'
  if (streamWriter.isWaiting.value) return '正在理解你的心愿，整理人生素材'
  if (streamWriter.isDraining.value) return '故事马上完成，正在收束最后一句'
  if (streamWriter.isStreaming.value) return '正在把你的心愿写成故事'
  return '正在把你的心愿写成故事'
})

const generationSubcopy = computed(() => {
  if (generationStatus.value === 'verySlow') return '如果网络波动，稍后会给你温和提示，不会丢掉当前心愿。'
  if (generationStatus.value === 'slow') return 'AI 还没有吐出第一句话，但请求仍在进行中。'
  if (streamWriter.isStreaming.value || streamWriter.isDraining.value) return '已经收到内容，正在逐字展示给你。'
  return generationHints[generationHintIndex.value]
})

const generationFailureCopy = computed(() => {
  return generationError.value || '可能是网络慢了，或 AI 服务暂时没有回应。你可以直接再试一次，也可以返回修改心愿。'
})

const ttsActionText = computed(() => {
  if (!currentResult.value?.id) return '播放'
  if (ttsPlayer.loading.value) return '生成中'
  return ttsPlayer.playing.value ? '暂停' : '播放'
})

const ttsActionIcon = computed(() => {
  return ttsPlayer.playing.value ? 'Ⅱ' : '▶'
})

const formatMessageTime = () => {
  const date = new Date()
  const hours = String(date.getHours()).padStart(2, '0')
  const minutes = String(date.getMinutes()).padStart(2, '0')
  return `${hours}:${minutes}`
}

const clearGenerationFeedbackTimers = () => {
  if (generationHintTimer) {
    clearInterval(generationHintTimer)
    generationHintTimer = null
  }
  if (generationSlowTimer) {
    clearTimeout(generationSlowTimer)
    generationSlowTimer = null
  }
  if (generationVerySlowTimer) {
    clearTimeout(generationVerySlowTimer)
    generationVerySlowTimer = null
  }
}

const startGenerationFeedback = () => {
  clearGenerationFeedbackTimers()
  generationStatus.value = 'waiting'
  generationError.value = ''
  generationHintIndex.value = 0
  generationHintTimer = setInterval(() => {
    if (generationStatus.value === 'failed' || visibleStreamContent.value) return
    generationHintIndex.value = (generationHintIndex.value + 1) % generationHints.length
  }, 3600)
  generationSlowTimer = setTimeout(() => {
    if (generating.value && !streamContent.value && generationStatus.value !== 'failed') {
      generationStatus.value = 'slow'
    }
  }, 8000)
  generationVerySlowTimer = setTimeout(() => {
    if (generating.value && !streamContent.value && generationStatus.value !== 'failed') {
      generationStatus.value = 'verySlow'
    }
  }, 20000)
}

const markGenerationStreaming = () => {
  if (generationStatus.value === 'failed') return
  generationStatus.value = 'streaming'
}

const markGenerationFailed = (message) => {
  clearGenerationFeedbackTimers()
  generationStatus.value = 'failed'
  generationError.value = message || '可能是网络慢了，或 AI 服务暂时没有回应。你可以直接再试一次，也可以返回修改心愿。'
  streamWriter.fail(generationError.value)
}

const normalizeGeneratedScript = (data) => {
  const script = data?.script || data || {}
  const latestScript = Array.isArray(store.scripts) && store.scripts.length ? store.scripts[0] : null
  const merged = script?.id ? script : { ...latestScript, ...script }
  const plotJson = merged?.plotJson || {}
  const content = normalizeGeneratedContent(merged?.content || merged?.plotJson?.fullContent || merged?.summary || '')

  return {
    id: merged?.id || '',
    title: merged?.title || wishText.value || '我的人生剧本',
    theme: merged?.theme || wishText.value,
    style: merged?.style || '爽文',
    length: merged?.length || 'medium',
    tags: merged?.tags || [merged?.style || '爽文', '成长', '被看见'],
    summary: normalizeGeneratedContent(merged?.summary || content.slice(0, 90)),
    content,
    plotJson,
    conversationId: merged?.conversationId || plotJson.conversationId || '',
    parentScriptId: merged?.parentScriptId || plotJson.parentScriptId || '',
    revisionIndex: Number(merged?.revisionIndex || plotJson.revisionIndex || 1),
    revisionOf: merged?.revisionOf || plotJson.revisionOf || ''
  }
}

const openScriptLibrary = () => {
  analytics.track('script_history_click', {}, { eventType: 'script', pagePath })
  uni.$emit('switchTab', 'library')
}

const useRecommendation = (text) => {
  analytics.track('script_inspiration_select', {
    source: 'recommendation',
    prompt_length: text.length
  }, { eventType: 'script', pagePath })
  wishText.value = text
}

const shuffleInspirations = async () => {
  analytics.track('script_inspiration_refresh', {
    source: 'home'
  }, { eventType: 'script', pagePath })
  const list = await store.fetchRandomInspirations(4)
  randomRecommendations.value = list.length ? list : fallbackRecommendations
}

const startVoicePress = () => {
  if (generating.value) return
  if (!recorderManager) {
    voiceState.value = 'error'
    analytics.track('script_voice_recognize_fail', {
      reason: 'recorder_unavailable'
    }, { eventType: 'script', pagePath })
    uni.showToast({ title: '当前环境不支持录音', icon: 'none' })
    setTimeout(() => {
      if (voiceState.value === 'error') voiceState.value = 'idle'
    }, 1800)
    return
  }
  recordCancelled = false
  recordStartedAt = Date.now()
  voiceState.value = 'pressing'
  analytics.track('script_voice_press_start', {}, { eventType: 'script', pagePath })
  recorderManager.start({
    duration: 60000,
    sampleRate: 16000,
    numberOfChannels: 1,
    encodeBitRate: 48000,
    format: 'mp3'
  })
}

const cancelVoicePress = () => {
  recordCancelled = true
  if (recorderManager && voiceState.value === 'pressing') {
    recorderManager.stop()
  } else {
    voiceState.value = 'idle'
  }
  analytics.track('script_voice_record_cancel', {}, { eventType: 'script', pagePath })
}

const endVoicePress = async () => {
  if (voiceState.value !== 'pressing') return
  analytics.track('script_voice_press_end', {}, { eventType: 'script', pagePath })
  voiceState.value = 'recognizing'
  recorderManager?.stop()
}

const handleVoiceRecognizeSuccess = (text, durationMs) => {
  if (viewState.value === 'result') {
    resultChatInput.value = text
    resultInputFocused.value = true
  } else {
    wishText.value = text
    homeInputFocused.value = true
  }
  voiceState.value = 'idle'
  analytics.track('script_voice_recognize_success', {
    text_length: text.length,
    duration_ms: durationMs || 0
  }, { eventType: 'script', pagePath })
  uni.showToast({ title: viewState.value === 'result' ? '识别成功，可继续修改建议' : '识别成功，可修改后发送', icon: 'none' })
}

const handleVoiceRecognizeFail = (reason, message = '语音识别失败，请重试') => {
  voiceState.value = 'error'
  analytics.track('script_voice_recognize_fail', {
    reason
  }, { eventType: 'script', pagePath })
  uni.showToast({ title: message, icon: 'none' })
  setTimeout(() => {
    if (voiceState.value === 'error') voiceState.value = 'idle'
  }, 1800)
}

const setupRecorder = () => {
  if (!uni.getRecorderManager) return
  recorderManager = uni.getRecorderManager()
  recorderManager.onStop(async (res) => {
    if (recordCancelled) {
      voiceState.value = 'idle'
      return
    }
    const duration = Date.now() - recordStartedAt
    if (!res?.tempFilePath || duration < 500) {
      handleVoiceRecognizeFail('record_too_short', '说话时间太短，请重试')
      return
    }
    voiceState.value = 'recognizing'
    try {
      const response = await transcribeAudio(res.tempFilePath)
      const text = response?.data?.text?.trim()
      if (!text) {
        handleVoiceRecognizeFail('empty_result', '没有识别到内容，请重试')
        return
      }
      handleVoiceRecognizeSuccess(text, response?.data?.durationMs)
    } catch (error) {
      handleVoiceRecognizeFail(error?.message || error?.errMsg || 'upload_failed')
    }
  })
  recorderManager.onError((error) => {
    handleVoiceRecognizeFail(error?.errMsg || 'recorder_error', '录音失败，请检查麦克风权限')
  })
}

const SCRIPT_CHAT_HISTORY_PREFIX = 'script_conversation_history_'
const LEGACY_SCRIPT_CHAT_HISTORY_PREFIX = 'script_chat_history_'
const PENDING_SCRIPT_CHAT_KEY = 'pending_open_script_chat'

const createMessageId = () => `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
const createConversationId = () => `conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`

const getCurrentScriptId = () => String(currentResult.value?.id || '')
const getCurrentConversationId = () => String(currentConversationId.value || currentResult.value?.conversationId || '')

const ensureConversationId = (script = currentResult.value) => {
  const existing = String(script?.conversationId || script?.plotJson?.conversationId || currentConversationId.value || '')
  currentConversationId.value = existing || createConversationId()
  return currentConversationId.value
}

const getScriptChatHistoryKey = (conversationId = getCurrentConversationId()) => {
  return conversationId ? `${SCRIPT_CHAT_HISTORY_PREFIX}${conversationId}` : ''
}

const readStoredMessages = (key) => {
  if (!key) return []
  const value = uni.getStorageSync(key)
  return Array.isArray(value) ? value.filter(item => {
    if (!item?.role) return false
    if (item.role === 'script') return false
    return typeof item.content === 'string'
  }) : []
}

const readScriptChatHistory = ({ conversationId, scriptId } = {}) => {
  const messages = readStoredMessages(getScriptChatHistoryKey(conversationId))
  if (messages.length || !scriptId) return messages
  return readStoredMessages(`${LEGACY_SCRIPT_CHAT_HISTORY_PREFIX}${scriptId}`)
}

const persistResultMessages = () => {
  const key = getScriptChatHistoryKey()
  if (!key) return
  const messages = resultMessages.value
    .filter(item => item?.role && item.role !== 'script' && !item.pending && item.content)
    .map(item => ({
      id: item.id,
      role: item.role,
      kind: item.kind || '',
      content: item.content,
      time: item.time
    }))
  uni.setStorageSync(key, messages)
}

const hydrateResultMessages = ({ conversationId, scriptId } = {}) => {
  resultMessages.value = readScriptChatHistory({ conversationId, scriptId }).map(item => ({
    id: item.id || createMessageId(),
    role: item.role,
    kind: item.kind || '',
    content: item.content || '',
    pending: false,
    time: item.time || formatMessageTime()
  }))
  collapsedMessageIds.value = {}
}

const addResultMessage = ({ role, content, pending = false, kind = '' }) => {
  const message = {
    id: createMessageId(),
    role,
    kind,
    content,
    pending,
    time: formatMessageTime()
  }
  resultMessages.value.push(message)
  if (!pending) persistResultMessages()
  keepResultAtBottom()
  return message
}

const getScriptVersions = (script = currentResult.value) => {
  const versions = script?.plotJson?.versions
  return Array.isArray(versions) ? versions : []
}

const createScriptVersion = ({ script, content, revisionIndex, prompt, source }) => ({
  id: `version-${revisionIndex}-${Date.now()}`,
  revisionIndex,
  title: script?.title || wishText.value || '我的人生剧本',
  content,
  summary: normalizeGeneratedContent(content).slice(0, 90),
  prompt: prompt || '',
  source: source || 'mini-program-stream',
  createdAt: new Date().toISOString()
})

const buildVersionedPlotJson = ({ baseScript, content, conversationId, revisionIndex, prompt, source }) => {
  const existingPlotJson = baseScript?.plotJson || {}
  const existingVersions = Array.isArray(existingPlotJson.versions) ? existingPlotJson.versions : []
  const nextVersion = createScriptVersion({
    script: baseScript,
    content,
    revisionIndex,
    prompt,
    source
  })
  return {
    ...existingPlotJson,
    mode: existingPlotJson.mode || 'inspiration',
    prompt,
    source,
    fullContent: content,
    conversationId,
    currentRevisionIndex: revisionIndex,
    versions: [...existingVersions, nextVersion]
  }
}


const buildRevisionPrompt = (suggestion) => {
  const history = resultMessages.value
    .filter(item => item.role === 'user' || item.role === 'assistant')
    .map(item => `${item.role === 'user' ? '用户' : 'AI'}：${item.content}`)
    .slice(-10)
    .join('\n')
  return [
    '请基于同一个对话中的上一版小说和用户最新修改需求，重新生成一篇完整中文爽文小说。',
    '要求：保留原始心愿的核心情绪，沿用上一版有效设定，只针对用户要求进行调衡和改写，输出完整正文，不要输出解释说明。',
    `对话ID：${getCurrentConversationId()}`,
    `原始心愿：${wishText.value || currentResult.value?.theme || ''}`,
    `上一版小说标题：${currentResult.value?.title || '我的人生剧本'}`,
    `上一版小说正文：${resultContent.value || ''}`,
    history ? `最近对话历史：\n${history}` : '',
    `用户修改需求：${suggestion}`
  ].filter(Boolean).join('\n\n')
}

const buildChatPrompt = (message) => {
  return [
    '你是如愿星球里帮助用户修改爽文小说的中文创作 agent。',
    '请围绕当前小说回应用户的修改意见，先帮助用户确认方向、给出简短建议，不要直接重写全文。',
    '如果用户表达已确认要重新生成，请提醒用户发送“确认生成”或给出更明确的修改方向。',
    `原始心愿：${wishText.value || ''}`,
    `对话ID：${getCurrentConversationId()}`,
    `当前小说标题：${currentResult.value?.title || '我的人生剧本'}`,
    `当前小说正文：${resultContent.value || ''}`,
    `用户消息：${message}`
  ].join('\n\n')
}

const normalizeCommandText = (message) => {
  return String(message || '')
    .replace(/\s+/g, '')
    .replace(/[，。！？、；：,.!?;:"'“”‘’]/g, '')
}

const shouldRegenerateFromMessage = (message) => {
  const text = normalizeCommandText(message)
  if (!text) return false
  const hasRegenerateAction = /(\u751f\u6210|\u91cd\u5199|\u91cd\u65b0)/u.test(text)
  const hasNegation = /(\u4e0d|\u522b|\u4e0d\u8981|\u5148\u4e0d|\u6682\u4e0d).*(\u751f\u6210|\u91cd\u5199|\u91cd\u65b0)/u.test(text)
  if (hasRegenerateAction && hasNegation) return false
  const exactCommands = new Set([
    '\u786e\u8ba4\u751f\u6210',
    '\u786e\u8ba4\u91cd\u5199',
    '\u5f00\u59cb\u751f\u6210',
    '\u5f00\u59cb\u91cd\u5199',
    '\u91cd\u65b0\u751f\u6210',
    '\u76f4\u63a5\u751f\u6210',
    '\u5c31\u8fd9\u6837\u751f\u6210',
    '\u5c31\u8fd9\u6837',
    '\u751f\u6210',
    '\u91cd\u5199'
  ])
  return exactCommands.has(text)
    || /\u786e\u8ba4.*(\u751f\u6210|\u91cd\u5199|\u91cd\u65b0)/u.test(text)
    || /(\u5f00\u59cb|\u76f4\u63a5|\u91cd\u65b0).*(\u751f\u6210|\u91cd\u5199)/u.test(text)
}

const removeLeadingRepeatedPrefix = (text) => {
  let output = String(text || '')
  let changed = true
  while (changed) {
    changed = false
    const maxPrefixLength = Math.floor(output.length / 2)
    for (let size = maxPrefixLength; size >= 80; size -= 1) {
      const prefix = output.slice(0, size)
      if (output.slice(size).startsWith(prefix)) {
        output = output.slice(size).trimStart()
        changed = true
        break
      }
    }
  }
  return output
}

const normalizeParagraphForCompare = (paragraph) => {
  return String(paragraph || '')
    .replace(/[#>*_`~\-【】「」『』“”"'，。！？、；：,.!?;:\s]/g, '')
    .trim()
}

const similarityRatio = (left, right) => {
  if (!left || !right) return 0
  const short = left.length <= right.length ? left : right
  const long = left.length > right.length ? left : right
  if (!short) return 0
  if (long.includes(short)) return short.length / long.length
  let same = 0
  const max = Math.min(left.length, right.length)
  for (let index = 0; index < max; index += 1) {
    if (left[index] === right[index]) same += 1
  }
  return same / Math.max(left.length, right.length)
}

const isRepeatedParagraph = (current, previous) => {
  const currentKey = normalizeParagraphForCompare(current)
  const previousKey = normalizeParagraphForCompare(previous)
  if (currentKey.length < 24 || previousKey.length < 24) return false
  if (currentKey === previousKey) return true
  return similarityRatio(currentKey, previousKey) >= 0.92
}

const removeRepeatedParagraphs = (text) => {
  const paragraphs = String(text || '').split(/\n{2,}/)
  const kept = []
  const recent = []
  paragraphs.forEach((paragraph) => {
    const trimmed = paragraph.trim()
    if (!trimmed) return
    const repeated = recent.some((item) => isRepeatedParagraph(trimmed, item))
    if (!repeated) {
      kept.push(trimmed)
      recent.push(trimmed)
      if (recent.length > 8) recent.shift()
    }
  })
  return kept.join('\n\n')
}

const normalizeGeneratedContent = (text) => {
  return removeRepeatedParagraphs(removeLeadingRepeatedPrefix(String(text || '').trim()))
}

const openScriptChat = async (payload = {}) => {
  uni.removeStorageSync(PENDING_SCRIPT_CHAT_KEY)
  const scriptId = String(payload?.id || payload?.scriptId || '')
  let script = payload?.script || (payload?.id ? payload : null)
  if (!script?.id && scriptId) {
    script = store.getScriptById(scriptId)
    if (!script) {
      await store.fetchScripts()
      script = store.getScriptById(scriptId)
    }
  }
  if (!script?.id) return

  clearGenerationFeedbackTimers()
  generating.value = false
  generationStatus.value = 'idle'
  generationError.value = ''
  streamContent.value = ''
  streamWriter.reset()
  resultChatWriter.reset()
  activeAssistantMessageId.value = ''
  resultChatting.value = false
  revisionIntent.value = ''
  resultChatInput.value = ''
  storyCollapsed.value = false
  ttsPlayer.reset()

  currentResult.value = normalizeGeneratedScript({ script })
  currentConversationId.value = ensureConversationId(currentResult.value)
  wishText.value = script.theme || script.prompt || script.title || '继续修改这个人生剧本'
  currentMessageTime.value = formatMessageTime()
  currentResultTime.value = script.updateTime || script.updatedAt || script.createTime || script.createdAt || formatMessageTime()
  hydrateResultMessages({ conversationId: currentConversationId.value, scriptId: script.id })
  viewState.value = 'result'
  analytics.track('script_chat_open_from_library', {
    script_id: script.id,
    has_history: resultMessages.value.length > 0
  }, { eventType: 'script', pagePath })
  scrollResultToLatest()
}

const openPendingScriptChat = async () => {
  const pending = uni.getStorageSync(PENDING_SCRIPT_CHAT_KEY)
  if (!pending) return
  uni.removeStorageSync(PENDING_SCRIPT_CHAT_KEY)
  await openScriptChat(pending)
}

const handleOpenScriptChat = (payload) => {
  openScriptChat(payload)
}

const saveGeneratedScriptContent = async ({
  prompt,
  content,
  saveTheme,
  conversationId,
  revisionIndex = 1,
  updateScriptId = '',
  parentScriptId = '',
  revisionOf = ''
}) => {
  const profile = store.userProfile || store.registrationData || {}
  const characterInfo = epicScriptService.buildCharacterInfo(profile)
  const lifeEventsSummary = epicScriptService.buildLifeEventsSummary(store.events || [], profile)
  const theme = saveTheme || prompt
  const baseScript = updateScriptId ? currentResult.value : {
    title: theme.length > 22 ? `${theme.slice(0, 22)}...` : theme,
    theme,
    style: style.value,
    length: 'medium',
    plotJson: {}
  }
  const title = baseScript?.title || (theme.length > 22 ? `${theme.slice(0, 22)}...` : theme)
  const plotJson = buildVersionedPlotJson({
    baseScript,
    content,
    conversationId,
    revisionIndex,
    prompt,
    source: 'mini-program-stream'
  })
  const payload = {
    id: updateScriptId || undefined,
    title,
    theme,
    style: baseScript?.style || style.value,
    length: baseScript?.length || 'medium',
    content,
    plotJson,
    conversationId,
    parentScriptId,
    revisionIndex,
    revisionOf,
    characterInfo,
    lifeEventsSummary,
    useSocialInsights: useSocialInsights.value
  }
  const saveRes = updateScriptId
    ? await store.updateScript(payload)
    : await store.createScript(payload)
  if (!saveRes.success) {
    throw new Error(saveRes.error || (updateScriptId ? '更新剧本失败' : '保存剧本失败'))
  }
  return {
    success: true,
    data: {
      script: saveRes.data,
      remainingCount: remainingCount.value
    }
  }
}

const generateScriptByStream = async (text, options = {}) => {
  const saveTheme = options.saveTheme || text
  const conversationId = options.conversationId || ensureConversationId()
  const revisionIndex = options.revisionIndex || 1
  const updateScriptId = options.updateScriptId || ''
  const streamRes = await streamAiScene({
    sceneCode: 'script_generate',
    inputs: {
      prompt: text,
      style: style.value,
      length: 'medium',
      useSocialInsights: useSocialInsights.value
    },
    onDelta: (_delta, output) => {
      const preview = normalizeGeneratedContent(output)
      streamContent.value = preview
      markGenerationStreaming()
      streamWriter.push(preview)
      scrollGenerationToLatest()
    },
    onDone: (_event, output) => {
      streamWriter.finish(normalizeGeneratedContent(output))
      keepGenerationAtBottom()
    },
    onError: (message) => {
      streamWriter.fail(message || 'AI 流式生成失败')
    }
  })
  const content = normalizeGeneratedContent(streamRes.output)
  if (!content) {
    throw new Error('AI 流式输出为空')
  }
  streamWriter.finish(content)
  await streamWriter.waitForDone()
  return await saveGeneratedScriptContent({
    prompt: text,
    content,
    saveTheme,
    conversationId,
    revisionIndex,
    parentScriptId: options.parentScriptId || '',
    revisionOf: options.revisionOf || '',
    updateScriptId
  })
}

const runGeneration = async ({ prompt, displayText, source = 'text', saveTheme, revision = false }) => {
  const text = String(prompt || '').trim()
  const display = String(displayText || text).trim()
  if (!text || generating.value) return
  const isRevision = Boolean(revision)
  const conversationId = isRevision ? ensureConversationId() : createConversationId()
  const parentScriptId = isRevision ? getCurrentScriptId() : ''
  const revisionIndex = isRevision
    ? getScriptVersions().length + 1
    : 1
  currentConversationId.value = conversationId
  lastGenerationRevision.value = isRevision
  lastSubmitSource.value = source
  generationDisplayText.value = display
  lastGenerationPrompt.value = text

  analytics.track('script_wish_submit', {
    source,
    prompt_length: display.length
  }, { eventType: 'script', pagePath })

  currentMessageTime.value = formatMessageTime()
  generationStartedAt.value = Date.now()
  generating.value = true
  streamContent.value = ''
  generationScrollTarget.value = ''
  generationScrollTop.value = 0
  generationScrollAnchor.value = 'generation-stream-anchor-a'
  generationAutoFollow.value = true
  streamWriter.reset()
  startGenerationFeedback()
  ttsPlayer.reset()
  viewState.value = 'generating'
  keepGenerationAtBottom()
  analytics.track('script_generation_progress_view', {
    source,
    prompt_length: display.length
  }, { eventType: 'script', pagePath })

  let res
  try {
    res = await generateScriptByStream(text, {
      saveTheme: saveTheme || display,
      conversationId,
      parentScriptId,
      revisionIndex,
      revisionOf: parentScriptId,
      updateScriptId: isRevision ? getCurrentScriptId() : ''
    })
  } catch (streamError) {
    markGenerationFailed(streamError?.message || 'AI 流式生成失败')
    analytics.track('script_generate_stream_fail', {
      source,
      error: streamError?.message || 'stream_failed'
    }, { eventType: 'script', pagePath })
    res = {
      success: false,
      error: streamError?.message || 'AI 流式生成失败'
    }
  }

  generating.value = false
  if (res.success) {
    clearGenerationFeedbackTimers()
    if (streamContent.value) streamWriter.finish(normalizeGeneratedContent(streamContent.value))
    keepGenerationAtBottom()
  }

  if (!res.success) {
    analytics.track('script_generate_fail', {
      source,
      error: res.error || 'unknown',
      duration_ms: Date.now() - generationStartedAt.value
    }, { eventType: 'script', pagePath })
    markGenerationFailed(res.error || '生成失败')
    return
  }

  currentResult.value = normalizeGeneratedScript(res.data)
  currentConversationId.value = ensureConversationId(currentResult.value)
  currentResultTime.value = formatMessageTime()
  if (!isRevision) {
    resultMessages.value = []
    collapsedMessageIds.value = {}
  }
  if (typeof res.data?.remainingCount === 'number') remainingCount.value = res.data.remainingCount

  analytics.track('script_generate_success', {
    source,
    style: currentResult.value.style || '',
    length: currentResult.value.length || '',
    use_social_insights: useSocialInsights.value,
    duration_ms: Date.now() - generationStartedAt.value
  }, { eventType: 'script', pagePath })
  analytics.track('script_result_view', {
    script_id: currentResult.value.id || '',
    style: currentResult.value.style || '',
    length: currentResult.value.length || ''
  }, { eventType: 'script', pagePath })

  generationStatus.value = 'idle'
  viewState.value = 'result'
  keepResultAtBottom()
}

const submitWish = async (source = 'text') => {
  const text = wishText.value.trim()
  if (!text || generating.value) return
  await runGeneration({ prompt: text, displayText: text, source, saveTheme: text })
}

const retryGeneration = () => {
  if (generating.value) return
  analytics.track('script_generation_retry_click', {
    prompt_length: wishText.value.trim().length
  }, { eventType: 'script', pagePath })
  generationStatus.value = 'idle'
  generationError.value = ''
  runGeneration({
    prompt: lastGenerationPrompt.value || wishText.value.trim(),
    displayText: wishText.value.trim(),
    source: lastSubmitSource.value || 'text',
    saveTheme: wishText.value.trim(),
    revision: lastGenerationRevision.value
  })
}

const returnToEdit = () => {
  clearGenerationFeedbackTimers()
  generating.value = false
  generationStatus.value = 'idle'
  generationError.value = ''
  streamContent.value = ''
  streamWriter.reset()
  analytics.track('script_generation_back_edit_click', {
    prompt_length: wishText.value.trim().length
  }, { eventType: 'script', pagePath })
  viewState.value = 'home'
}

const closeResult = () => {
  clearGenerationFeedbackTimers()
  generationStatus.value = 'idle'
  viewState.value = 'home'
  currentResult.value = null
  currentConversationId.value = ''
  resultMessages.value = []
  collapsedMessageIds.value = {}
  resultChatInput.value = ''
  revisionIntent.value = ''
  storyCollapsed.value = false
  ttsPlayer.reset()
}

const toggleStoryCollapse = () => {
  storyCollapsed.value = !storyCollapsed.value
  analytics.track('script_result_story_collapse_toggle', {
    script_id: currentResult.value?.id || '',
    collapsed: storyCollapsed.value
  }, { eventType: 'script', pagePath })
}

const copyResultContent = () => {
  const content = resultContent.value || ''
  if (!content.trim()) {
    uni.showToast({ title: '暂无可复制内容', icon: 'none' })
    return
  }
  uni.setClipboardData({
    data: content,
    success: () => {
      uni.showToast({ title: '已复制全文', icon: 'success' })
    }
  })
  analytics.track('script_result_copy_click', {
    script_id: currentResult.value?.id || '',
    content_length: content.length
  }, { eventType: 'script', pagePath })
}

const continueInChat = () => {
  storyCollapsed.value = true
  resultInputFocus.value = true
  setTimeout(() => {
    resultInputFocus.value = false
  }, 300)
  scrollResultToLatest()
  analytics.track('script_result_continue_click', {
    script_id: currentResult.value?.id || ''
  }, { eventType: 'script', pagePath })
}

const enterRevisionConfirm = (intent, copy) => {
  revisionIntent.value = intent
  resultChatInput.value = ''
  addResultMessage({
    role: 'assistant',
    content: copy
  })
  scrollResultToLatest()
}

const changeDirection = () => {
  analytics.track('script_result_change_direction_click', {
    script_id: currentResult.value?.id || ''
  }, { eventType: 'script', pagePath })
  enterRevisionConfirm('change_direction', '我先不直接重写。你想换成更事业逆袭、更情感治愈，还是更强反转的方向？告诉我一句，我确认后再重新生成。')
}

const notLikeMe = () => {
  analytics.track('script_result_not_like_me_click', {
    script_id: currentResult.value?.id || ''
  }, { eventType: 'script', pagePath })
  enterRevisionConfirm('not_like_me', '收到，我会先帮你确认“不像你”的地方。你可以说哪里不贴近你，比如性格、经历、情绪或结局，我再基于当前故事重写。')
}

const regenerateFromRevision = async (message, source = 'result_chat') => {
  const conversationId = ensureConversationId()
  const prompt = buildRevisionPrompt(message)
  const parentScriptId = getCurrentScriptId()
  const revisionIndex = getScriptVersions().length + 1
  revisionIntent.value = ''
  resultChatInput.value = ''
  resultChatting.value = true
  storyCollapsed.value = false
  resultChatWriter.reset()
  const assistant = addResultMessage({
    role: 'assistant',
    kind: 'script',
    content: '',
    pending: true
  })
  activeAssistantMessageId.value = assistant.id

  let generatedContent = ''
  try {
    const response = await streamAiScene({
      sceneCode: 'script_generate',
      inputs: {
        prompt,
        style: style.value,
        length: 'medium',
        useSocialInsights: useSocialInsights.value
      },
      onDelta: (_delta, output) => {
        const normalized = normalizeGeneratedContent(output)
        // 避免内容回退时重置导致的闪烁；只推进增量内容
        if (normalized.length >= resultChatWriter.visibleText.value.length) {
          resultChatWriter.push(normalized)
        }
        keepResultAtBottom()
      },
      onDone: (_event, output) => {
        resultChatWriter.finish(normalizeGeneratedContent(output))
        keepResultAtBottom()
      },
      onError: (errorMessage) => {
        resultChatWriter.fail(errorMessage || 'AI 流式生成失败')
      }
    })
    generatedContent = normalizeGeneratedContent(response.output)
    if (!generatedContent) {
      throw new Error('AI 流式输出为空')
    }
    resultChatWriter.finish(generatedContent)
    await resultChatWriter.waitForDone()
    const current = resultMessages.value.find(item => item.id === assistant.id)
    if (current) current.content = generatedContent

    const saveRes = await saveGeneratedScriptContent({
      prompt,
      content: generatedContent,
      saveTheme: wishText.value || currentResult.value?.theme || currentResult.value?.title || '根据当前故事继续修改',
      conversationId,
      revisionIndex,
      updateScriptId: parentScriptId,
      parentScriptId,
      revisionOf: parentScriptId
    })
    currentResult.value = normalizeGeneratedScript(saveRes.data)
    currentConversationId.value = ensureConversationId(currentResult.value)
    currentResultTime.value = formatMessageTime()
    if (typeof saveRes.data?.remainingCount === 'number') remainingCount.value = saveRes.data.remainingCount
    analytics.track('script_generate_success', {
      source,
      style: currentResult.value.style || '',
      length: currentResult.value.length || '',
      use_social_insights: useSocialInsights.value,
      duration_ms: 0
    }, { eventType: 'script', pagePath })
  } catch (error) {
    resultChatWriter.fail(error?.message || '这次生成没有成功，可以稍后再试。')
    const current = resultMessages.value.find(item => item.id === assistant.id)
    if (current && !current.content) {
      current.content = error?.message || '这次生成没有成功，可以稍后再试。'
    }
    analytics.track('script_generate_fail', {
      source,
      error: error?.message || 'unknown'
    }, { eventType: 'script', pagePath })
  } finally {
    const current = resultMessages.value.find(item => item.id === assistant.id)
    if (current) {
      current.pending = false
      current.kind = generatedContent ? 'script' : ''
      if (!current.content && resultChatWriter.visibleText.value) current.content = resultChatWriter.visibleText.value
    }
    activeAssistantMessageId.value = ''
    resultChatting.value = false
    persistResultMessages()
    keepResultAtBottom()
  }
}

const sendResultChat = async (source = 'text') => {
  const message = resultChatInput.value.trim()
  if (!message || resultChatting.value || generating.value) return
  resultChatInput.value = ''
  addResultMessage({ role: 'user', content: message })
  analytics.track('script_result_chat_send', {
    source,
    script_id: currentResult.value?.id || '',
    has_revision_intent: Boolean(revisionIntent.value),
    message_length: message.length
  }, { eventType: 'script', pagePath })

  if (revisionIntent.value || shouldRegenerateFromMessage(message)) {
    await regenerateFromRevision(message, source)
    return
  }

  resultChatting.value = true
  resultChatWriter.reset()
  const assistant = addResultMessage({
    role: 'assistant',
    content: '',
    pending: true
  })
  activeAssistantMessageId.value = assistant.id

  try {
    const response = await streamAiScene({
      sceneCode: 'script_generate',
      inputs: {
        prompt: buildChatPrompt(message),
        style: style.value,
        length: 'short',
        useSocialInsights: false
      },
      onDelta: (_delta, output) => {
        const text = String(output || '')
        if (text.length >= resultChatWriter.visibleText.value.length) {
          resultChatWriter.push(text)
        }
        keepResultAtBottom()
      },
      onDone: (_event, output) => {
        resultChatWriter.finish(String(output || ''))
        keepResultAtBottom()
      },
      onError: (errorMessage) => {
        resultChatWriter.fail(errorMessage || '暂时没有收到回复')
      }
    })
    const output = response.output?.trim() || '我收到了。你可以继续补充，也可以直接说“确认生成”。'
    resultChatWriter.finish(output)
    await resultChatWriter.waitForDone()
    const current = resultMessages.value.find(item => item.id === assistant.id)
    if (current) current.content = output
  } catch (error) {
    resultChatWriter.fail(error?.message || '这次回复没有成功，可以稍后再试。')
    const current = resultMessages.value.find(item => item.id === assistant.id)
    if (current && !current.content) {
      current.content = error?.message || '这次回复没有成功，可以稍后再试。'
    }
  } finally {
    const current = resultMessages.value.find(item => item.id === assistant.id)
    if (current) {
      current.pending = false
      if (!current.content && resultChatWriter.visibleText.value) {
        current.content = resultChatWriter.visibleText.value
      }
    }
    activeAssistantMessageId.value = ''
    resultChatting.value = false
    persistResultMessages()
    keepResultAtBottom()
  }
}

const trackTtsClick = () => {
  analytics.track('script_result_tts_click', {
    script_id: currentResult.value?.id || ''
  }, { eventType: 'tts', pagePath })
  ttsPlayer.playSource(currentResult.value?.id || '')
}

onMounted(() => {
  analytics.track('script_home_view', {}, { eventType: 'script', pagePath })
  setupRecorder()
  uni.$on('openScriptChat', handleOpenScriptChat)
  pendingOpenTimer = setTimeout(openPendingScriptChat, 300)
})

onUnmounted(() => {
  clearGenerationFeedbackTimers()
  if (generationScrollTimer) {
    clearTimeout(generationScrollTimer)
    generationScrollTimer = null
  }
  if (resultScrollTargetTimer) {
    clearTimeout(resultScrollTargetTimer)
    resultScrollTargetTimer = null
  }
  if (pendingOpenTimer) {
    clearTimeout(pendingOpenTimer)
    pendingOpenTimer = null
  }
  streamWriter.dispose()
  resultChatWriter.dispose()
  uni.$off('openScriptChat', handleOpenScriptChat)
  if (recorderManager && voiceState.value === 'pressing') {
    recordCancelled = true
    recorderManager.stop()
  }
})
</script>

<style scoped>
.script-view {
  position: relative;
  height: 100%;
  min-height: 100%;
  overflow: hidden;
  color: #fff;
  font-family: "PingFang SC", "Noto Sans SC", sans-serif;
}

.cosmic-background {
  position: absolute;
  inset: -120rpx -140rpx;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.cosmic-stars {
  position: absolute;
  inset: 0;
  opacity: 0.48;
}

.cosmic-stars.layer-one {
  background:
    radial-gradient(circle at 12% 18%, rgba(255, 255, 255, 0.52) 0 2rpx, transparent 3rpx),
    radial-gradient(circle at 32% 9%, rgba(255, 216, 107, 0.42) 0 2rpx, transparent 3rpx),
    radial-gradient(circle at 74% 32%, rgba(255, 255, 255, 0.36) 0 2rpx, transparent 3rpx),
    radial-gradient(circle at 88% 48%, rgba(209, 138, 255, 0.42) 0 2rpx, transparent 3rpx),
    radial-gradient(circle at 24% 72%, rgba(255, 255, 255, 0.32) 0 2rpx, transparent 3rpx);
  animation: starFloat 8s ease-in-out infinite;
}

.cosmic-stars.layer-two {
  opacity: 0.32;
  background:
    radial-gradient(circle at 18% 42%, rgba(255, 255, 255, 0.5) 0 1rpx, transparent 2rpx),
    radial-gradient(circle at 46% 28%, rgba(209, 138, 255, 0.5) 0 1rpx, transparent 2rpx),
    radial-gradient(circle at 64% 70%, rgba(255, 216, 107, 0.5) 0 1rpx, transparent 2rpx),
    radial-gradient(circle at 92% 18%, rgba(255, 255, 255, 0.42) 0 1rpx, transparent 2rpx);
  animation: starFloat 11s ease-in-out infinite reverse;
}

.cosmic-planet {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}

.planet-main {
  top: 210rpx;
  right: -120rpx;
  width: 260rpx;
  height: 260rpx;
  opacity: 0.18;
  background:
    radial-gradient(circle at 42% 42%, rgba(209, 138, 255, 0.5), rgba(93, 38, 193, 0.22) 42%, transparent 72%);
  filter: blur(2rpx);
  box-shadow: 0 0 120rpx rgba(168, 85, 247, 0.22);
  animation: planetDrift 9s ease-in-out infinite;
}

.planet-main::after {
  content: '';
  position: absolute;
  left: 14rpx;
  top: 120rpx;
  width: 228rpx;
  height: 30rpx;
  border-radius: 50%;
  border: 3rpx solid rgba(255, 216, 107, 0.08);
  transform: rotate(-16deg);
}

.planet-soft {
  left: -64rpx;
  bottom: 180rpx;
  width: 128rpx;
  height: 128rpx;
  opacity: 0.2;
  background: radial-gradient(circle at 50% 42%, rgba(140, 68, 242, 0.78), rgba(30, 8, 76, 0.92));
  box-shadow: 0 0 64rpx rgba(140, 68, 242, 0.28);
  animation: planetDrift 12s ease-in-out infinite reverse;
}

.meteor {
  position: absolute;
  width: 160rpx;
  height: 4rpx;
  border-radius: 999rpx;
  background: linear-gradient(90deg, transparent 0%, rgba(132, 92, 255, 0.12) 18%, rgba(255, 216, 107, 0.34) 58%, rgba(255, 255, 255, 0.95) 100%);
  opacity: 0;
  transform: rotate(22deg);
  animation: meteorFall 5.6s cubic-bezier(0.18, 0.65, 0.42, 1) infinite;
  box-shadow: 0 0 18rpx rgba(255, 216, 107, 0.14);
}

.meteor::after {
  content: '';
  position: absolute;
  right: -5rpx;
  top: 50%;
  width: 10rpx;
  height: 10rpx;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.96);
  box-shadow:
    0 0 14rpx rgba(255, 255, 255, 0.74),
    0 0 28rpx rgba(255, 216, 107, 0.34);
  transform: translateY(-50%);
}

.meteor-one {
  top: 120rpx;
  left: -190rpx;
}

.meteor-two {
  top: 260rpx;
  left: 46rpx;
  width: 126rpx;
  animation-delay: 1.8s;
}

.meteor-three {
  top: 440rpx;
  left: 420rpx;
  width: 118rpx;
  animation-delay: 3.4s;
}

.meteor-four {
  top: 360rpx;
  left: -210rpx;
  width: 210rpx;
  height: 5rpx;
  opacity: 0;
  animation-duration: 7.2s;
  animation-delay: 4.6s;
}

.meteor-five {
  top: 720rpx;
  left: -180rpx;
  width: 148rpx;
  animation-duration: 6.4s;
  animation-delay: 2.8s;
}

@keyframes starFloat {
  0%, 100% {
    transform: translateY(0);
    opacity: 0.36;
  }

  50% {
    transform: translateY(18rpx);
    opacity: 0.62;
  }
}

@keyframes planetDrift {
  0%, 100% {
    transform: translate3d(0, 0, 0) scale(1);
  }

  50% {
    transform: translate3d(-14rpx, 18rpx, 0) scale(1.03);
  }
}

@keyframes meteorFall {
  0% {
    opacity: 0;
    transform: translate3d(0, 0, 0) rotate(22deg);
  }

  8% {
    opacity: 0.76;
  }

  32% {
    opacity: 0.18;
  }

  38% {
    opacity: 0;
    transform: translate3d(660rpx, 270rpx, 0) rotate(22deg);
  }

  100% {
    opacity: 0;
    transform: translate3d(660rpx, 270rpx, 0) rotate(22deg);
  }
}

@keyframes micPulse {
  0%, 100% {
    opacity: 0.22;
    transform: scale(0.92);
  }

  50% {
    opacity: 0.48;
    transform: scale(1.02);
  }
}

@keyframes micHaloBreath {
  0%, 100% {
    opacity: 0.76;
    transform: scale(0.94);
  }

  50% {
    opacity: 1;
    transform: scale(1.04);
  }
}

@keyframes micIdleBreath {
  0%, 100% {
    transform: translateY(0) scale(1);
  }

  50% {
    transform: translateY(-4rpx) scale(1.015);
  }
}

@keyframes micPressBreath {
  0%, 100% {
    transform: scale(1.055);
  }

  50% {
    transform: scale(1.085);
  }
}

@keyframes micWavePress {
  0% {
    opacity: 0.58;
    transform: scale(0.82);
  }

  100% {
    opacity: 0;
    transform: scale(1.22);
  }
}

@keyframes micScan {
  0% {
    background-position: -180rpx 0, 0 0, 0 0;
  }

  100% {
    background-position: 180rpx 0, 0 0, 0 0;
  }
}

@keyframes micHighlightFloat {
  0%, 100% {
    opacity: 0.24;
    transform: translateY(0);
  }

  50% {
    opacity: 0.42;
    transform: translateY(8rpx);
  }
}

.wish-home,
.generation-view,
.result-page {
  position: relative;
  z-index: 1;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  padding: 4rpx 28rpx 24rpx;
}

.wish-home {
  gap: 36rpx;
  padding-bottom: 28rpx;
}

.result-page {
  height: 100%;
  min-height: 0;
}

.result-view {
  position: relative;
  z-index: 1;
  flex: 1;
  height: 100%;
  min-height: 0;
}

.result-scroll-content {
  min-height: 100%;
  box-sizing: border-box;
  padding: 0 0 220rpx;
}

.result-scroll-top {
  width: 1rpx;
  height: 1rpx;
}

.result-top-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18rpx;
}

.page-close-btn {
  width: 64rpx;
  height: 64rpx;
  line-height: 60rpx;
  padding: 0;
  border-radius: 50%;
  color: rgba(255, 255, 255, 0.92);
  background: rgba(255, 255, 255, 0.06);
  border: 1rpx solid rgba(192, 132, 252, 0.36);
  font-size: 42rpx;
  backdrop-filter: blur(18rpx);
  -webkit-backdrop-filter: blur(18rpx);
}

.page-close-btn::after {
  border: 0;
}

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

.history-button {
  height: 58rpx;
  display: inline-flex;
  align-items: center;
  gap: 12rpx;
  padding: 0 20rpx;
  border-radius: 999rpx;
  color: rgba(232, 204, 255, 0.92);
  font-size: 24rpx;
  font-weight: 800;
  background: rgba(43, 19, 83, 0.46);
  border: 1rpx solid rgba(168, 85, 247, 0.24);
}

.history-lines {
  width: 28rpx;
  display: flex;
  flex-direction: column;
  gap: 5rpx;
}

.history-lines view {
  height: 3rpx;
  border-radius: 999rpx;
  background: currentColor;
}

.hero-copy {
  margin-top: 26rpx;
  margin-bottom: 30rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8rpx;
  text-align: center;
  overflow: visible;
}

.hero-title {
  display: none;
}

.hero-title-new {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8rpx;
}

.hero-title-line {
  display: flex;
  align-items: baseline;
  justify-content: center;
  white-space: nowrap;
  font-size: 60rpx;
  font-weight: 800;
  line-height: 1.18;
  letter-spacing: 0;
}

.hero-title-tail {
  font-size: 58rpx;
}

.hero-highlight {
  margin: 0 6rpx;
  color: #d18aff;
  text-shadow: 0 0 28rpx rgba(209, 138, 255, 0.52);
}

.orb-wrap {
  position: relative;
  height: 300rpx;
  margin-top: 44rpx;
  margin-bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.orb-wrap::before {
  content: '';
  position: absolute;
  width: 420rpx;
  height: 420rpx;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(116, 41, 210, 0.42), transparent 64%);
  filter: blur(6rpx);
  animation: micHaloBreath 3.8s ease-in-out infinite;
}

.mic-orb {
  position: relative;
  width: 260rpx;
  height: 260rpx;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(145deg, #f1a0ff 0%, #934dff 48%, #4d1ccb 100%);
  box-shadow:
    0 0 72rpx rgba(169, 85, 247, 0.75),
    0 0 180rpx rgba(102, 41, 201, 0.55);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
  overflow: hidden;
  animation: micIdleBreath 3.2s ease-in-out infinite;
}

.mic-orb::before {
  content: '';
  position: absolute;
  inset: 18rpx;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 26%, rgba(255, 255, 255, 0.28), transparent 18%),
    radial-gradient(circle at 50% 62%, rgba(39, 16, 98, 0.18), transparent 48%);
  border: 1rpx solid rgba(255, 255, 255, 0.1);
}

.mic-orb::after {
  content: '';
  position: absolute;
  inset: -18rpx;
  border-radius: 50%;
  border: 3rpx solid rgba(216, 180, 254, 0.12);
  animation: micPulse 2.8s ease-in-out infinite;
}

.mic-orb.pressing {
  animation: micPressBreath 1.1s ease-in-out infinite;
  transform: scale(1.06);
  box-shadow:
    0 0 86rpx rgba(241, 160, 255, 0.82),
    0 0 220rpx rgba(102, 41, 201, 0.68);
}

.mic-orb.pressing::after {
  border-color: rgba(255, 216, 107, 0.26);
  animation: micWavePress 0.95s ease-out infinite;
}

.mic-orb.recognizing {
  opacity: 0.86;
}

.mic-orb.recognizing::before {
  background:
    linear-gradient(110deg, transparent 0 34%, rgba(255, 255, 255, 0.24) 44%, transparent 56%),
    radial-gradient(circle at 35% 26%, rgba(255, 255, 255, 0.28), transparent 18%),
    radial-gradient(circle at 50% 62%, rgba(39, 16, 98, 0.18), transparent 48%);
  animation: micScan 1.35s linear infinite;
}

.mic-symbol {
  position: relative;
  width: 118rpx;
  height: 156rpx;
  z-index: 2;
}

.mic-head {
  position: relative;
  width: 66rpx;
  height: 106rpx;
  margin: 0 auto;
  border-radius: 999rpx;
  border: 10rpx solid rgba(255, 255, 255, 0.96);
  box-sizing: border-box;
  background: transparent;
  box-shadow:
    inset 0 0 14rpx rgba(255, 255, 255, 0.12),
    0 0 16rpx rgba(255, 255, 255, 0.22);
}

.mic-head::before {
  content: '';
  position: absolute;
  left: 9rpx;
  top: 16rpx;
  width: 8rpx;
  height: 34rpx;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.18);
  animation: micHighlightFloat 2.6s ease-in-out infinite;
}

.mic-symbol::after {
  content: '';
  position: absolute;
  left: 8rpx;
  top: 56rpx;
  width: 102rpx;
  height: 70rpx;
  box-sizing: border-box;
  border-left: 10rpx solid rgba(255, 255, 255, 0.95);
  border-right: 10rpx solid rgba(255, 255, 255, 0.95);
  border-bottom: 10rpx solid rgba(255, 255, 255, 0.95);
  border-radius: 0 0 64rpx 64rpx;
  box-shadow: 0 0 14rpx rgba(255, 255, 255, 0.18);
}

.mic-stem {
  width: 12rpx;
  height: 34rpx;
  margin: -4rpx auto 0;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.96);
  box-shadow: 0 0 12rpx rgba(255, 255, 255, 0.2);
}

.mic-base {
  width: 76rpx;
  height: 12rpx;
  margin: 0 auto;
  border-radius: 999rpx;
  background: rgba(255, 255, 255, 0.96);
  box-shadow: 0 0 12rpx rgba(255, 255, 255, 0.2);
}

.voice-copy {
  margin-top: -2rpx;
  margin-bottom: 46rpx;
  text-align: center;
  font-size: 34rpx;
  font-weight: 500;
  line-height: 56rpx;
  color: rgba(255, 255, 255, 0.92);
}

.wish-input-wrap {
  position: relative;
  min-height: 92rpx;
  margin-top: 0;
  margin-bottom: 6rpx;
  display: flex;
  align-items: center;
  gap: 14rpx;
  padding: 12rpx 14rpx 12rpx 28rpx;
  border-radius: 52rpx;
  background: linear-gradient(180deg, rgba(43, 19, 83, 0.72), rgba(32, 14, 61, 0.66));
  border: 1rpx solid rgba(168, 85, 247, 0.42);
  box-shadow: 0 0 22rpx rgba(116, 52, 202, 0.12);
  transition: min-height 0.22s ease, border-radius 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
}

.wish-input-wrap.active {
  border-color: rgba(209, 138, 255, 0.62);
  box-shadow: 0 0 28rpx rgba(168, 85, 247, 0.18);
}

.wish-input-wrap.twoLine {
  min-height: 126rpx;
  align-items: flex-end;
  border-radius: 42rpx;
}

.wish-input-wrap.expanded {
  min-height: 158rpx;
  align-items: flex-end;
  border-radius: 36rpx;
}

.custom-input-placeholder {
  position: absolute;
  z-index: 1;
  color: rgba(216, 180, 254, 0.48);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}

.home-input-placeholder {
  left: 28rpx;
  right: 138rpx;
  top: 50%;
  transform: translateY(-50%);
  font-size: 34rpx;
  line-height: 1;
}

.wish-input {
  position: relative;
  z-index: 2;
  flex: 1;
  height: 72rpx;
  min-height: 72rpx;
  max-height: 150rpx;
  padding: 0;
  box-sizing: border-box;
  color: #fff;
  font-size: 34rpx;
  line-height: 72rpx;
}

.wish-input-wrap.twoLine .wish-input,
.wish-input-wrap.expanded .wish-input {
  height: auto;
  padding: 8rpx 0;
  line-height: 48rpx;
}

.send-button {
  position: relative;
  z-index: 2;
  min-width: 104rpx;
  height: 72rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999rpx;
  color: #fff;
  font-size: 28rpx;
  font-weight: 700;
  background: linear-gradient(145deg, #934dff, #4d1ccb);
}

.send-button.disabled {
  opacity: 0.45;
}

.inspiration-section {
  display: flex;
  flex-direction: column;
  gap: 24rpx;
  margin-top: 4rpx;
  margin-bottom: 8rpx;
}

.section-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.section-title {
  font-size: 30rpx;
  font-weight: 800;
}

.refresh {
  font-size: 30rpx;
  font-weight: 800;
  color: #e8ccff;
}

.recommend-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 22rpx 24rpx;
}

.recommend-card {
  min-height: 68rpx;
  display: flex;
  align-items: center;
  min-width: 0;
  padding: 0 22rpx;
  border-radius: 24rpx;
  background: linear-gradient(180deg, rgba(48, 24, 89, 0.78), rgba(32, 14, 62, 0.76));
  border: 1rpx solid rgba(168, 85, 247, 0.22);
  box-sizing: border-box;
}

.recommend-text {
  width: 100%;
  min-width: 0;
  font-size: 26rpx;
  line-height: 68rpx;
  color: rgba(255, 255, 255, 0.92);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.generation-view {
  flex: 1;
  height: 100%;
  min-height: 0;
  padding: 0;
}

.generation-scroll {
  width: 100%;
  height: 100%;
  min-height: 0;
  box-sizing: border-box;
}

.generation-scroll-content {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  gap: 42rpx;
  box-sizing: border-box;
  padding: 40rpx 0 190rpx;
}

.generation-scroll-anchor {
  width: 1rpx;
  height: 28rpx;
}

.conversation {
  display: flex;
  flex-direction: column;
  gap: 20rpx;
}

.conversation.compact {
  margin-bottom: 26rpx;
}

.chat-bubble {
  max-width: 86%;
  display: flex;
  flex-direction: column;
  gap: 8rpx;
  padding: 24rpx 28rpx;
  border-radius: 36rpx;
  font-size: 34rpx;
  line-height: 52rpx;
}

.chat-bubble.user {
  align-self: flex-end;
  background: linear-gradient(145deg, rgba(140, 68, 242, 0.72), rgba(95, 29, 184, 0.64));
  color: #fff;
  border: 1rpx solid rgba(209, 138, 255, 0.16);
  backdrop-filter: blur(10rpx);
  -webkit-backdrop-filter: blur(10rpx);
}

.chat-bubble.system {
  align-self: flex-start;
  background: rgba(16, 8, 34, 0.28);
  border: 1rpx solid rgba(192, 132, 252, 0.3);
  color: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(8rpx);
  -webkit-backdrop-filter: blur(8rpx);
}

.chat-bubble.pending {
  border-color: rgba(255, 216, 107, 0.28);
}

.message-toggle {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8rpx;
  padding: 8rpx 18rpx;
  border-radius: 999rpx;
  color: #ffd86b;
  font-size: 24rpx;
  line-height: 32rpx;
  font-weight: 700;
  background: rgba(140, 68, 242, 0.18);
  border: 1rpx solid rgba(255, 216, 107, 0.18);
}

.message-toggle-icon {
  font-size: 22rpx;
  line-height: 28rpx;
  transition: transform 0.2s ease;
}

.message-toggle-icon.open {
  transform: rotate(180deg);
}

.thinking-dots {
  display: flex;
  align-items: center;
  gap: 10rpx;
  padding-top: 8rpx;
}

.thinking-dots view {
  width: 12rpx;
  height: 12rpx;
  border-radius: 50%;
  background: #ffd86b;
  box-shadow: 0 0 18rpx rgba(255, 216, 107, 0.52);
  animation: thinkingDot 1.2s ease-in-out infinite;
}

.thinking-dots view:nth-child(2) {
  animation-delay: 0.18s;
  background: #d18aff;
}

.thinking-dots view:nth-child(3) {
  animation-delay: 0.36s;
  background: #8c44f2;
}

@keyframes thinkingDot {
  0%, 80%, 100% {
    opacity: 0.38;
    transform: translateY(0);
  }

  40% {
    opacity: 1;
    transform: translateY(-8rpx);
  }
}

.stream-preview {
  display: block;
  color: rgba(255, 255, 255, 0.86);
  font-size: 28rpx;
  line-height: 44rpx;
  white-space: pre-wrap;
  word-break: break-all;
}

.stream-preview.typing {
  padding-top: 12rpx;
}

.typing-cursor {
  color: #ffd86b;
  animation: cursorBlink 0.9s steps(2, start) infinite;
}

.generation-error {
  display: block;
  color: rgba(255, 231, 163, 0.92);
  font-size: 28rpx;
  line-height: 44rpx;
  white-space: pre-wrap;
}

@keyframes cursorBlink {
  0%, 45% {
    opacity: 1;
  }

  46%, 100% {
    opacity: 0;
  }
}

.chat-bubble.done {
  border-color: rgba(192, 132, 252, 0.42);
}

.bubble-time {
  font-size: 24rpx;
  line-height: 32rpx;
  color: rgba(255, 255, 255, 0.65);
}

.loading-orbit {
  position: relative;
  width: 180rpx;
  height: 180rpx;
  align-self: center;
  border-radius: 50%;
  border: 3rpx solid rgba(192, 132, 252, 0.28);
  box-shadow: 0 0 44rpx rgba(168, 85, 247, 0.55);
  animation: orbitBreath 2.4s ease-in-out infinite;
  transition: opacity 0.2s ease, filter 0.2s ease;
}

.generation-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28rpx;
}

.loading-orbit.streaming {
  filter: saturate(1.2);
}

.loading-orbit.failed {
  opacity: 0.52;
  filter: grayscale(0.2);
}

.loading-orbit::after {
  content: '';
  position: absolute;
  right: 18rpx;
  top: 20rpx;
  width: 24rpx;
  height: 24rpx;
  border-radius: 50%;
  background: #ffd86b;
  box-shadow: 0 0 24rpx rgba(255, 216, 107, 0.72);
  animation: orbitLight 1.8s linear infinite;
  transform-origin: -72rpx 70rpx;
}

.orbit-ring {
  position: absolute;
  inset: -22rpx;
  border-radius: 50%;
  border: 1rpx solid rgba(255, 216, 107, 0.16);
  animation: ringPulse 2.8s ease-in-out infinite;
}

.orbit-ring.inner {
  inset: 18rpx;
  border-color: rgba(209, 138, 255, 0.2);
  animation-delay: 0.7s;
}

.orbit-core {
  position: absolute;
  inset: 42rpx;
  border-radius: 50%;
  background: radial-gradient(circle, #c084fc, #5c1bb0);
  box-shadow: inset 0 0 22rpx rgba(255, 255, 255, 0.18);
}

@keyframes orbitBreath {
  0%, 100% {
    transform: scale(0.96);
    box-shadow: 0 0 36rpx rgba(168, 85, 247, 0.42);
  }

  50% {
    transform: scale(1.04);
    box-shadow: 0 0 70rpx rgba(168, 85, 247, 0.66);
  }
}

@keyframes orbitLight {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

@keyframes ringPulse {
  0%, 100% {
    opacity: 0.2;
    transform: scale(0.92);
  }

  50% {
    opacity: 0.78;
    transform: scale(1.08);
  }
}

.loading-copy {
  text-align: center;
  font-size: 30rpx;
  color: rgba(255, 255, 255, 0.75);
}

.loading-subcopy {
  display: block;
  max-width: 620rpx;
  text-align: center;
  padding: 18rpx 24rpx;
  border-radius: 28rpx;
  color: rgba(255, 231, 163, 0.78);
  font-size: 24rpx;
  line-height: 36rpx;
  background: rgba(255, 216, 107, 0.07);
  border: 1rpx solid rgba(255, 216, 107, 0.14);
}

.generation-actions {
  display: flex;
  justify-content: center;
  gap: 18rpx;
}

.generation-action {
  min-width: 176rpx;
  height: 72rpx;
  margin: 0;
  padding: 0 28rpx;
  border-radius: 999rpx;
  font-size: 27rpx;
  line-height: 72rpx;
}

.generation-action::after {
  border: 0;
}

.generation-action.primary {
  color: #1b0b31;
  background: linear-gradient(145deg, #ffd86b, #d18aff);
  box-shadow: 0 16rpx 36rpx rgba(209, 138, 255, 0.24);
}

.generation-action.secondary {
  color: #e8ccff;
  background: rgba(43, 19, 83, 0.68);
  border: 1rpx solid rgba(168, 85, 247, 0.32);
}

.story-card {
  border-radius: 52rpx;
  padding: 34rpx;
  background: rgba(12, 5, 28, 0.34);
  border: 1rpx solid rgba(192, 132, 252, 0.46);
  box-shadow: 0 0 48rpx rgba(125, 55, 205, 0.14);
  backdrop-filter: blur(6rpx);
  -webkit-backdrop-filter: blur(6rpx);
}

.story-card.collapsed {
  box-shadow: 0 0 42rpx rgba(125, 55, 205, 0.14);
}

.story-head {
  display: flex;
  align-items: flex-start;
  gap: 20rpx;
}

.story-head-actions {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12rpx;
}

.story-title-wrap {
  flex: 1;
  min-width: 0;
}

.story-title {
  display: block;
  font-size: 52rpx;
  font-weight: 700;
  line-height: 1.35;
}

.collapse-icon {
  width: 120rpx;
  min-width: 120rpx;
  height: 58rpx;
  min-height: 58rpx;
  line-height: 1;
  padding: 0 18rpx;
  border-radius: 999rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8rpx;
  color: rgba(255, 244, 255, 0.95);
  background:
    linear-gradient(135deg, rgba(134, 72, 255, 0.44), rgba(39, 15, 76, 0.42)),
    rgba(255, 255, 255, 0.05);
  border: 1rpx solid rgba(216, 180, 254, 0.46);
  box-shadow:
    0 14rpx 30rpx rgba(61, 18, 113, 0.22),
    inset 0 1rpx 0 rgba(255, 255, 255, 0.26),
    inset 0 -10rpx 18rpx rgba(33, 9, 73, 0.16);
  backdrop-filter: blur(8rpx);
  -webkit-backdrop-filter: blur(8rpx);
  font-size: 23rpx;
  font-weight: 800;
  letter-spacing: 0;
}

.collapse-icon::after {
  border: 0;
}

.collapse-icon:active,
.copy-card-btn:active,
.collapse-row:active {
  transform: scale(0.98);
  opacity: 0.92;
}

.copy-card-btn {
  width: 120rpx;
  min-width: 120rpx;
  height: 50rpx;
  min-height: 50rpx;
  line-height: 1;
  padding: 0;
  border-radius: 999rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 244, 255, 0.9);
  font-size: 23rpx;
  font-weight: 800;
  letter-spacing: 0;
  background:
    radial-gradient(circle at 50% -30%, rgba(255, 255, 255, 0.18), transparent 50%),
    rgba(88, 28, 135, 0.26);
  border: 1rpx solid rgba(216, 180, 254, 0.32);
  box-shadow:
    inset 0 1rpx 0 rgba(255, 255, 255, 0.16),
    0 10rpx 22rpx rgba(61, 18, 113, 0.12);
  backdrop-filter: blur(8rpx);
  -webkit-backdrop-filter: blur(8rpx);
}

.copy-card-btn::after {
  border: 0;
}

.collapse-icon-text,
.collapse-row-text {
  line-height: 1;
  white-space: nowrap;
}

.collapse-chevron {
  position: relative;
  width: 20rpx;
  height: 16rpx;
  flex-shrink: 0;
  transition: transform 0.18s ease;
}

.collapse-chevron.down {
  transform: rotate(180deg);
}

.collapse-chevron view {
  position: absolute;
  top: 7rpx;
  width: 12rpx;
  height: 4rpx;
  border-radius: 999rpx;
  background: linear-gradient(90deg, #fff3b0, #ffd86b);
  box-shadow: 0 0 12rpx rgba(255, 216, 107, 0.5);
}

.collapse-chevron view:first-child {
  left: 0;
  transform: rotate(-38deg);
  transform-origin: right center;
}

.collapse-chevron view:last-child {
  right: 0;
  transform: rotate(38deg);
  transform-origin: left center;
}

.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12rpx;
  margin-top: 18rpx;
}

.tag {
  padding: 6rpx 16rpx;
  border-radius: 999rpx;
  color: #d18aff;
  font-size: 24rpx;
  background: rgba(168, 85, 247, 0.22);
}

.story-body {
  display: block;
  margin-top: 28rpx;
  font-size: 32rpx;
  font-weight: 400;
  line-height: 1.78;
  color: rgba(255, 255, 255, 0.92);
  white-space: pre-wrap;
}

.story-body-scroll {
  max-height: 420rpx;
  height: auto;
  margin-top: 28rpx;
  padding-right: 8rpx;
  box-sizing: border-box;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.story-body-scroll .story-body {
  margin-top: 0;
}

.collapse-row {
  position: relative;
  height: 62rpx;
  margin-top: 22rpx;
  padding: 0 28rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12rpx;
  border-radius: 999rpx;
  color: rgba(246, 230, 255, 0.96);
  font-size: 26rpx;
  font-weight: 800;
  overflow: hidden;
  background:
    radial-gradient(circle at 50% -20%, rgba(255, 255, 255, 0.2), transparent 46%),
    linear-gradient(135deg, rgba(98, 40, 174, 0.3), rgba(24, 8, 58, 0.24));
  border: 1rpx solid rgba(192, 132, 252, 0.36);
  box-shadow:
    inset 0 1rpx 0 rgba(255, 255, 255, 0.16),
    0 12rpx 28rpx rgba(61, 18, 113, 0.14);
  backdrop-filter: blur(6rpx);
  -webkit-backdrop-filter: blur(6rpx);
}

.collapse-row::before {
  content: '';
  position: absolute;
  left: 34rpx;
  right: 34rpx;
  top: 0;
  height: 1rpx;
  background: linear-gradient(90deg, transparent, rgba(255, 236, 180, 0.6), transparent);
}

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

.action-btn {
  height: 72rpx;
  min-height: 72rpx;
  padding: 0 8rpx;
  border-radius: 28rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #e8ccff;
  font-size: 26rpx;
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  white-space: normal;
  box-sizing: border-box;
  background: rgba(88, 28, 135, 0.18);
  border: 1rpx solid rgba(192, 132, 252, 0.35);
}

.action-btn.primary {
  color: #fff;
  background: linear-gradient(145deg, #8c44f2, #5f1db8);
}

.action-icon {
  margin-right: 8rpx;
  font-size: 24rpx;
  font-weight: 900;
  line-height: 1;
}

.action-btn::after {
  border: 0;
}

.result-chat-list {
  display: flex;
  flex-direction: column;
  gap: 18rpx;
  margin-top: 24rpx;
}

.result-scroll-anchor {
  width: 1rpx;
  height: 28rpx;
}

.scroll-jump-group {
  position: absolute;
  right: 22rpx;
  bottom: 258rpx;
  z-index: 13;
  display: flex;
  flex-direction: column;
  gap: 12rpx;
}

.scroll-jump-btn {
  width: 86rpx;
  height: 58rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999rpx;
  color: #e8ccff;
  font-size: 23rpx;
  font-weight: 900;
  background: rgba(43, 19, 83, 0.72);
  border: 1rpx solid rgba(192, 132, 252, 0.34);
  box-shadow: 0 12rpx 34rpx rgba(0, 0, 0, 0.28);
  backdrop-filter: blur(18rpx);
  -webkit-backdrop-filter: blur(18rpx);
}

.result-chat-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 12;
  min-height: 104rpx;
  display: flex;
  align-items: flex-end;
  gap: 12rpx;
  padding: 14rpx 0 22rpx;
  box-sizing: border-box;
  background: linear-gradient(180deg, rgba(5, 2, 13, 0), rgba(5, 2, 13, 0.9) 30%, rgba(5, 2, 13, 0.96));
}

.chat-voice-btn {
  width: 86rpx;
  height: 76rpx;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 26rpx;
  color: #e8ccff;
  font-size: 24rpx;
  font-weight: 800;
  background: rgba(88, 28, 135, 0.32);
  border: 1rpx solid rgba(192, 132, 252, 0.3);
}

.chat-voice-btn.pressing,
.chat-voice-btn.recognizing {
  color: #fff;
  background: linear-gradient(145deg, #934dff, #4d1ccb);
  box-shadow: 0 0 30rpx rgba(168, 85, 247, 0.38);
}

.result-input-shell {
  position: relative;
  flex: 1;
  height: 76rpx;
  min-height: 76rpx;
  max-height: 146rpx;
  box-sizing: border-box;
  border-radius: 28rpx;
  background: rgba(43, 19, 83, 0.72);
  border: 1rpx solid rgba(168, 85, 247, 0.36);
  overflow: hidden;
}

.result-input-shell.twoLine {
  height: auto;
  min-height: 110rpx;
  border-radius: 30rpx;
}

.result-input-shell.expanded {
  height: auto;
  min-height: 144rpx;
  border-radius: 30rpx;
}

.result-input-placeholder {
  left: 22rpx;
  right: 22rpx;
  top: 50%;
  transform: translateY(-50%);
  font-size: 28rpx;
  line-height: 1;
}

.result-chat-input {
  position: relative;
  z-index: 2;
  width: 100%;
  height: 76rpx;
  min-height: 76rpx;
  max-height: 146rpx;
  padding: 0 22rpx;
  box-sizing: border-box;
  color: #fff;
  font-size: 28rpx;
  line-height: 76rpx;
  background: transparent;
}

.result-input-shell.twoLine .result-chat-input,
.result-input-shell.expanded .result-chat-input {
  height: auto;
  min-height: 78rpx;
  padding: 16rpx 22rpx;
  line-height: 40rpx;
}

.chat-send-btn {
  width: 92rpx;
  height: 76rpx;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 26rpx;
  color: #fff;
  font-size: 26rpx;
  font-weight: 800;
  background: linear-gradient(145deg, #934dff, #4d1ccb);
}

.chat-send-btn.disabled {
  opacity: 0.45;
}
</style>
