<script lang="ts">
export default { name: 'CreatingView' }
</script>
<template>
  <div class="creating-page">
    <!-- 进度环 -->
    <div class="ring-wrap">
      <svg width="180" height="180" class="ring-svg">
        <defs>
          <linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" stop-color="#6366f1" />
            <stop offset="50%" stop-color="#8b5cf6" />
            <stop offset="100%" stop-color="#ec4899" />
          </linearGradient>
        </defs>
        <circle cx="90" cy="90" r="80" fill="none" stroke="rgba(99, 102, 241, 0.12)" stroke-width="8" />
        <circle cx="90" cy="90" r="80" fill="none" stroke="url(#ringGrad)" stroke-width="8" :stroke-dasharray="502"
          :stroke-dashoffset="502 - (502 * progress / 100)" stroke-linecap="round" class="ring-fill" />
      </svg>
      <div class="ring-center">
        <div class="ring-pct">{{ progress }}%</div>
        <div class="ring-label">创作进度</div>
      </div>
    </div>

    <!-- 状态文字 -->
    <div class="stage-text">{{ stage }}</div>

    <!-- 轮转创作 tips -->
    <div v-if="!error" class="rotating-tips">
      <Transition name="tip-fade" mode="out-in">
        <div class="rotating-tip" :key="currentTipIdx">{{ creatingTips[currentTipIdx] }}</div>
      </Transition>
    </div>

    <!-- 网络波动提示 -->
    <div v-if="networkWarn && !error" class="network-warn">
      <wifi-outlined />
      <span>网络不太稳定，正在尝试重新连接{{ dots }}</span>
    </div>

    <!-- 错误重试 -->
    <div v-if="error" class="error-box">
      <frown-outlined class="error-icon" />
      <div class="error-text">{{ error }}</div>
      <div class="error-actions">
        <button v-if="store.workId" class="btn-primary error-btn" @click="resumePolling">
          恢复查询进度
        </button>
        <button v-if="!isQuotaError" class="btn-primary error-btn" :class="{ 'btn-outline': !!store.workId }"
          @click="retry">
          重新创作
        </button>
      </div>
    </div>

    <!-- 任务式说明 + 离开入口 -->
    <div v-if="!error" class="task-hint">
      <div class="task-hint-row">
        <cloud-server-outlined class="task-icon" />
        <span>AI 正在后台为你创作绘本，可以随时离开</span>
      </div>
      <button class="leave-btn" @click="leaveToWorks">
        <inbox-outlined />
        <span>去逛逛，看看其他作品</span>
      </button>
      <div class="task-hint-sub">完成后会自动出现在「作品库 · 草稿」</div>
    </div>
    <div v-else class="task-hint">
      <button class="leave-btn" @click="leaveToWorks">
        <inbox-outlined />
        <span>去逛逛，看看其他作品</span>
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onActivated, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { Client } from '@stomp/stompjs'
import {
  FrownOutlined,
  WifiOutlined,
  CloudServerOutlined,
  InboxOutlined,
} from '@ant-design/icons-vue'
import { useAicreateStore } from '@/stores/aicreate'
import { createStory, getWorkDetail } from '@/api/aicreate'
import { STATUS, getRouteByStatus } from '@/utils/aicreate/status'
import { clearExtractDraft } from '@/utils/aicreate/extractDraft'
import config from '@/utils/aicreate/config'

const router = useRouter()
const store = useAicreateStore()
const progress = ref(0)
const stage = ref('准备中…')
const dots = ref('')
const error = ref('')
/** 额度/次数限制类错误，不应显示"重新创作"按钮 */
const isQuotaError = computed(() => {
  const msg = error.value
  return msg.includes('次数已达上限') || msg.includes('额度不足')
})
const networkWarn = ref(false)
const currentTipIdx = ref(0)
const creatingTips = [
  'AI 正在为你构思故事',
  '画笔正在绘制插画',
  '故事世界正在成形',
  '角色们正在准备登场',
  '色彩正在调和',
]

let pollTimer: ReturnType<typeof setInterval> | null = null
let dotTimer: ReturnType<typeof setInterval> | null = null
let tipTimer: ReturnType<typeof setInterval> | null = null
let stompClient: any = null
let wsDegraded = false  // WebSocket 已降级到轮询，防止重复降级
let submitted = false
let consecutiveErrors = 0
const MAX_SILENT_ERRORS = 3
const MAX_POLL_ERRORS = 15

// 错误消息脱敏
function sanitizeError(msg: string | undefined): string {
  if (!msg) return '创作遇到问题，请重新尝试'
  if (msg.includes('调用次数已达上限') || msg.includes('30004')) return '今日创作次数已达上限，请明天再试'
  if (msg.includes('额度') || msg.includes('30003')) return '创作额度不足，请联系管理员'
  if (msg.includes('400') || msg.includes('status code')) return '创作请求异常，请返回重新操作'
  if (msg.includes('火山引擎') || msg.includes('VolcEngine')) return 'AI 服务暂时繁忙，请稍后重试'
  if (msg.includes('额度')) return msg
  if (msg.includes('重复') || msg.includes('DUPLICATE')) return '你有正在创作的作品，请等待完成'
  if (msg.includes('timeout') || msg.includes('超时')) return '创作超时，请重新尝试'
  if (msg.includes('OSS') || msg.includes('MiniMax')) return '服务暂时不可用，请稍后重试'
  if (msg.length > 50) return '创作遇到问题，请重新尝试'
  return msg
}

// 后端进度消息 → 用户友好阶段文案（不带任何 emoji 前缀）
function friendlyStage(pct: number, msg: string): string {
  if (!msg) return '创作中…'
  if (msg.includes('创作完成')) return '绘本创作完成'
  if (msg.includes('绘图完成') || msg.includes('绘制完成')) return '插画绘制完成'
  if (msg.includes('第') && msg.includes('组')) return '正在绘制插画…'
  if (msg.includes('绘图') || msg.includes('绘制') || msg.includes('插画')) return '正在绘制插画…'
  if (msg.includes('补生成')) return '正在绘制插画…'
  if (msg.includes('语音合成') || msg.includes('配音')) return '正在合成语音…'
  if (msg.includes('故事') && msg.includes('完成')) return '故事编写完成，开始绘图…'
  if (msg.includes('故事') || msg.includes('创作故事')) return '正在编写故事…'
  if (msg.includes('适配') || msg.includes('角色')) return '正在准备绘图…'
  if (msg.includes('重试')) return '遇到小问题，正在重新创作…'
  if (msg.includes('失败')) return '处理中，请稍候…'
  if (pct < 20) return '正在提交创作…'
  if (pct < 50) return '正在编写故事…'
  if (pct < 80) return '正在绘制插画…'
  if (pct < 100) return '即将完成…'
  return '绘本创作完成'
}

// 持久化 workId 到 localStorage，页面刷新后可恢复轮询
function saveWorkId(id: string) {
  store.workId = id
  if (id) {
    localStorage.setItem('le_workId', id)
  } else {
    localStorage.removeItem('le_workId')
  }
}

function restoreWorkId() {
  if (!store.workId) {
    store.workId = localStorage.getItem('le_workId') || ''
  }
}

/** 创作已推进到预览/配音等后续步骤时清除 extract 本地草稿 */
function replaceWhenCreationAdvances(route: ReturnType<typeof getRouteByStatus>) {
  if (!route) return
  if (route.name !== 'PublicCreateCreating') {
    clearExtractDraft()
  }
  setTimeout(() => router.replace(route), 800)
}

// ─── WebSocket 实时推送 (首次进入使用) ───
const startWebSocket = (workId: string) => {
  wsDegraded = false
  const wsBase = config.wsBaseUrl
    ? config.wsBaseUrl
    : `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}`
  const wsUrl = `${wsBase}/ws/websocket?orgId=${encodeURIComponent(store.orgId)}`

  stompClient = new Client({
    brokerURL: wsUrl,
    reconnectDelay: 0,
    onConnect: () => {
      stompClient.subscribe(`/topic/progress/${workId}`, (msg: any) => {
        try {
          const data = JSON.parse(msg.body)
          if (data.progress != null && data.progress > progress.value) progress.value = data.progress
          if (data.message) stage.value = friendlyStage(data.progress, data.message)

          if (data.progress >= 100) {
            progress.value = 100
            stage.value = '绘本创作完成'
            closeWebSocket()
            saveWorkId('')
            const route = getRouteByStatus(STATUS.COMPLETED, workId)
            if (route) {
              setTimeout(() => router.replace(route), 800)
            }
          } else if (data.progress < 0) {
            closeWebSocket()
            saveWorkId('')
            error.value = sanitizeError(data.message)
          }
        } catch { /* ignore parse error */ }
      })
    },
    onStompError: () => {
      if (wsDegraded) return
      wsDegraded = true
      closeWebSocket()
      startPolling(workId)
    },
    onWebSocketError: () => {
      if (wsDegraded) return
      wsDegraded = true
      closeWebSocket()
      startPolling(workId)
    },
    onWebSocketClose: () => {
      if (wsDegraded) return
      if (store.workId) {
        wsDegraded = true
        closeWebSocket()
        startPolling(workId)
      }
    }
  })
  stompClient.activate()
}

const closeWebSocket = () => {
  if (stompClient) {
    try { stompClient.deactivate() } catch { /* ignore */ }
    stompClient = null
  }
}
async function getWorkDetailApi(workId: string) {
  try {
    const work = await getWorkDetail(workId)
    if (!work) return

    if (consecutiveErrors > 0 || networkWarn.value) {
      consecutiveErrors = 0
      networkWarn.value = false
    }

    if (work.progress != null && work.progress > progress.value) progress.value = work.progress
    if (work.progressMessage) stage.value = friendlyStage(progress.value, work.progressMessage)

    if (work.status >= STATUS.COMPLETED) {
      progress.value = 100
      stage.value = '绘本创作完成'
      clearInterval(pollTimer!)
      pollTimer = null
      saveWorkId('')
      const route = getRouteByStatus(work.status, workId)
      if (route) replaceWhenCreationAdvances(route)
    } else if (work.status === STATUS.FAILED) {
      clearInterval(pollTimer!)
      pollTimer = null
      saveWorkId('')
      error.value = sanitizeError(work.failReason)
    }
  } catch {
    consecutiveErrors++
    if (consecutiveErrors > MAX_POLL_ERRORS) {
      clearInterval(pollTimer!)
      pollTimer = null
      networkWarn.value = false
      error.value = '网络连接异常，创作仍在后台进行中'
    } else if (consecutiveErrors > MAX_SILENT_ERRORS) {
      networkWarn.value = true
    }
  }
}
// ─── B2 轮询 ───
const startPolling = async (workId: string) => {
  if (pollTimer) clearInterval(pollTimer)
  consecutiveErrors = 0
  networkWarn.value = false

  pollTimer = setInterval(async () => {
    await getWorkDetailApi(workId);
  }, 8000);
  try {
    await getWorkDetailApi(workId);
  } catch (error) {
    console.log('error', error);
  }
}

const startCreation = async () => {
  if (submitted) return
  submitted = true

  error.value = ''
  progress.value = 5
  stage.value = '正在提交创作请求…'

  try {
    const res = await createStory({
      imageUrl: store.imageUrl,
      storyHint: store.storyData?.storyHint || '',
      style: store.selectedStyle,
      title: store.storyData?.title || '',
      heroName: store.storyData?.heroName || '',
      author: store.storyData?.author,
      heroCharId: store.selectedCharacter?.charId,
      extractId: store.extractId,
    })

    const workId = res?.workId
    if (!workId) {
      error.value = res.msg || '创作提交失败'
      submitted = false
      return
    }

    saveWorkId(workId)
    progress.value = 0
    stage.value = '故事构思中…'
    // startWebSocket(workId)
    startPolling(store.workId)
  } catch (e: any) {
    console.error('e', e);
    if (store.workId) {
      progress.value = 0
      stage.value = '创作已提交到后台…'
      startPolling(store.workId)
    } else {
      error.value = sanitizeError(e.message)
      submitted = false
    }
  }
}

const resumePolling = () => {
  error.value = ''
  networkWarn.value = false
  progress.value = 0
  stage.value = '正在查询创作进度…'
  startPolling(store.workId)
}

const retry = () => {
  saveWorkId('')
  submitted = false
  startCreation()
}

const leaveToWorks = () => {
  // 关闭前端监听，但后端任务继续；store.workId 仍在 localStorage，下次进入 CreatingView 会恢复
  closeWebSocket()
  if (pollTimer) { clearInterval(pollTimer); pollTimer = null }
  router.push('/p/works?tab=draft')
}

onMounted(() => {
  dotTimer = setInterval(() => {
    dots.value = dots.value.length >= 3 ? '' : dots.value + '.'
  }, 500)

  tipTimer = setInterval(() => {
    currentTipIdx.value = (currentTipIdx.value + 1) % creatingTips.length
  }, 3500)
  // 恢复 workId
  const urlWorkId = new URLSearchParams(window.location.search).get('workId')
  console.log('store.workId', urlWorkId, window.location.search)
  if (urlWorkId) {
    saveWorkId(urlWorkId)
  } else {
    restoreWorkId()
  }
  try {
    getWorkDetailApi(store.workId)
  } catch (error) {
    console.log('error', error);
  }
  if (store.workId) {
    submitted = true
    progress.value = 0
    stage.value = '正在查询创作进度…'
    startPolling(store.workId)
  } else {
    startCreation()
  }
})

onActivated(() => {
  const urlWorkId = new URLSearchParams(window.location.search).get('workId')
  if (urlWorkId) {
    saveWorkId(urlWorkId)
  } else {
    restoreWorkId()
  }
  if (store.workId) {
    void getWorkDetailApi(store.workId)
  }
})

onUnmounted(() => {
  closeWebSocket()
  if (pollTimer) clearInterval(pollTimer)
  if (dotTimer) clearInterval(dotTimer)
  if (tipTimer) clearInterval(tipTimer)
})
</script>

<style lang="scss" scoped>
.creating-page {
  min-height: 100vh;
  background: var(--ai-bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px 20px 32px;
  position: relative;
}

/* ---------- 进度环 ---------- */
.ring-wrap {
  position: relative;
  width: 180px;
  height: 180px;
  margin-bottom: 28px;
}

.ring-svg {
  transform: rotate(-90deg);
}

.ring-fill {
  transition: stroke-dashoffset 0.8s ease;
}

.ring-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.ring-pct {
  font-size: 38px;
  font-weight: 900;
  background: var(--ai-gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  letter-spacing: -1px;
}

.ring-label {
  font-size: 12px;
  color: var(--ai-text-sub);
  margin-top: 2px;
  letter-spacing: 1px;
}

/* ---------- 阶段文字 ---------- */
.stage-text {
  font-size: 17px;
  font-weight: 700;
  text-align: center;
  color: var(--ai-text);
}

/* ---------- 轮转 tips ---------- */
.rotating-tips {
  margin-top: 12px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.rotating-tip {
  font-size: 13px;
  color: var(--ai-text-sub);
  font-weight: 500;
  text-align: center;
  letter-spacing: 0.3px;
}

.tip-fade-enter-active,
.tip-fade-leave-active {
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.tip-fade-enter-from {
  opacity: 0;
  transform: translateY(8px);
}

.tip-fade-leave-to {
  opacity: 0;
  transform: translateY(-8px);
}

/* ---------- 网络警告 ---------- */
.network-warn {
  margin-top: 14px;
  padding: 6px 14px;
  border-radius: 12px;
  background: rgba(245, 158, 11, 0.08);
  color: #d97706;
  font-size: 12px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 6px;

  :deep(.anticon) {
    font-size: 13px;
  }
}

/* ---------- 错误状态 ---------- */
.error-box {
  margin-top: 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.error-icon {
  font-size: 44px;
  color: var(--ai-text-sub);
  margin-bottom: 12px;
}

.error-text {
  color: #ef4444;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.6;
  max-width: 280px;
}

.error-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 18px;
  width: 100%;
  max-width: 240px;
}

.error-btn {
  font-size: 14px !important;
  padding: 12px 0 !important;
  border-radius: 24px !important;
}

.error-btn.btn-outline {
  background: transparent !important;
  color: var(--ai-primary) !important;
  border: 1.5px solid rgba(99, 102, 241, 0.3) !important;
  box-shadow: none !important;
}

/* ---------- 任务式说明 + 离开按钮 ---------- */
.task-hint {
  margin-top: 36px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: 100%;
  max-width: 320px;
}

.task-hint-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--ai-text);
  font-weight: 500;
  text-align: center;
}

.task-icon {
  font-size: 15px;
  color: var(--ai-primary);
  flex-shrink: 0;
}

.task-hint-sub {
  font-size: 11px;
  color: var(--ai-text-sub);
  text-align: center;
}

.leave-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 22px;
  border-radius: 22px;
  background: #fff;
  color: var(--ai-primary);
  font-size: 14px;
  font-weight: 600;
  border: 1.5px solid rgba(99, 102, 241, 0.3);
  box-shadow: 0 2px 10px rgba(99, 102, 241, 0.06);
  cursor: pointer;
  transition: all 0.2s;

  :deep(.anticon) {
    font-size: 15px;
  }

  &:hover {
    border-color: var(--ai-primary);
    background: rgba(99, 102, 241, 0.04);
    box-shadow: 0 4px 14px rgba(99, 102, 241, 0.12);
  }

  &:active {
    transform: scale(0.98);
  }
}
</style>
