<script lang="ts">
export default { name: 'UploadView' }
</script>
<template>
  <div class="upload-page page-fullscreen">
    <PageHeader title="上传作品" subtitle="上传你的画作，AI 自动识别角色" :step="0" />

    <div class="content page-content">
      <template v-if="!preview">
        <!-- 上传区域 -->
        <div class="upload-area card">
          <template v-if="uploading">
            <cloud-upload-outlined class="uploading-icon" />
            <div class="uploading-text">正在上传...</div>
            <div class="progress-bar">
              <div class="progress-fill" />
            </div>
          </template>
          <template v-else>
            <div class="upload-icon-wrap">
              <picture-outlined class="upload-icon" />
            </div>
            <div class="upload-title">上传你的画作</div>
            <div class="upload-desc">支持拍照或从相册选择<br />AI 会自动识别画中的角色</div>
          </template>
        </div>

        <!-- 拍照/相册按钮 -->
        <div class="action-btns">
          <div class="action-btn camera" @click="pickImage('camera')">
            <camera-outlined class="action-icon" />
            <div class="action-label">拍照</div>
            <input ref="cameraInput" type="file" accept="image/*" capture="environment" @change="onFileChange"
              style="display:none" />
          </div>
          <div class="action-btn album" @click="pickImage('album')">
            <folder-open-outlined class="action-icon" />
            <div class="action-label">相册</div>
            <input ref="albumInput" type="file" accept="image/*" @change="onFileChange" style="display:none" />
          </div>
        </div>
      </template>

      <template v-else>
        <!-- 预览 -->
        <div class="preview-card card">
          <div class="preview-image">
            <img :src="preview" alt="预览" />
          </div>
          <!-- 识别中 -->
          <div v-if="uploading" class="recognizing-box">
            <loading-outlined class="recognizing-spinner" spin />
            <div class="recognizing-text">{{ uploadProgress || 'AI 正在识别你的角色...' }}</div>
          </div>
          <div v-else class="preview-info">
            <check-circle-filled class="preview-ok-icon" />
            <span class="preview-ok-text">已选择图片</span>
          </div>
        </div>

        <!-- 等待内容 -->
        <div v-if="uploading" class="waiting-content">
          <div class="waiting-card">
            <div class="waiting-title">AI 正在为你做这些事</div>
            <div class="waiting-steps">
              <div class="w-step" :class="{ active: uploadStage >= 1 }">
                <cloud-upload-outlined class="w-icon" />
                <span>上传画作到云端</span>
                <check-outlined v-if="uploadStage >= 1" class="w-done" />
              </div>
              <div class="w-step" :class="{ active: uploadStage >= 2 }">
                <eye-outlined class="w-icon" />
                <span>AI 识别画中角色</span>
                <check-outlined v-if="uploadStage >= 2" class="w-done" />
              </div>
              <div class="w-step" :class="{ active: uploadStage >= 3 }">
                <team-outlined class="w-icon" />
                <span>提取角色特征</span>
              </div>
            </div>
          </div>
          <div class="waiting-funfact">
            <bulb-outlined class="ff-icon" />
            <span class="ff-text">小知识：AI 画师可以识别超过 100 种不同的卡通角色</span>
          </div>
        </div>

        <div v-if="!quotaOk" class="quota-warn">{{ quotaMsg }}</div>
        <div v-if="uploadError" class="upload-error">{{ uploadError }}</div>
        <div class="preview-actions">
          <button class="btn-ghost" @click="reset">重新上传</button>
          <button class="btn-primary preview-next-btn" :disabled="uploading" @click="goNext">
            <template v-if="uploading">
              <loading-outlined spin />
              <span>AI 识别中...</span>
            </template>
            <template v-else>
              <span>识别角色</span>
              <arrow-right-outlined />
            </template>
          </button>
        </div>
      </template>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onActivated } from 'vue'
import { useRouter } from 'vue-router'
import PageHeader from '@/components/aicreate/PageHeader.vue'
import { useAicreateStore } from '@/stores/aicreate'
import { extractCharacters, ossUpload, checkQuota } from '@/api/aicreate'
import { saveExtractDraft } from '@/utils/aicreate/extractDraft'
import {
  PictureOutlined,
  CameraOutlined,
  FolderOpenOutlined,
  CloudUploadOutlined,
  LoadingOutlined,
  CheckOutlined,
  CheckCircleFilled,
  EyeOutlined,
  TeamOutlined,
  BulbOutlined,
  ArrowRightOutlined,
} from '@ant-design/icons-vue'

const router = useRouter()
const store = useAicreateStore()
const preview = ref<string | null>(null)
const uploading = ref(false)
const cameraInput = ref<HTMLInputElement | null>(null)
const albumInput = ref<HTMLInputElement | null>(null)
const fileSizeInfo = ref('')
const compressed = ref(false)
const saveOriginal = ref(false)
const uploadProgress = ref('')
const uploadStage = ref(0) // 0=未开始 1=上传中 2=识别中 3=提取中
const quotaOk = ref(true)
const quotaMsg = ref('')
let selectedFile: File | null = null

async function refreshQuota() {
  try {
    await checkQuota()
    quotaOk.value = true
  } catch (e: any) {
    const msg = e.message || ''
    if (msg.includes('额度') || msg.includes('QUOTA') || msg.includes('30003')) {
      quotaOk.value = false
      quotaMsg.value = '创作额度不足，请联系管理员'
    }
  }
}

onActivated(refreshQuota)

const pickImage = (type: string) => {
  if (type === 'camera') cameraInput.value?.click()
  else albumInput.value?.click()
}

/**
 * Canvas 压缩：将超高清图片缩放到 maxWidth，输出 JPEG 质量 quality
 * 目标：手机原片 8-20MB → 压缩后 <2MB
 */
const MAX_WIDTH = 1920
const MAX_SIZE = 2 * 1024 * 1024 // 2MB
const JPEG_QUALITY = 0.85

function compressImage(file: File): Promise<File> {
  return new Promise((resolve) => {
    // 小于 2MB 的图片不压缩
    if (file.size <= MAX_SIZE) {
      resolve(file)
      return
    }

    const img = new Image()
    const url = URL.createObjectURL(file)
    img.onload = () => {
      URL.revokeObjectURL(url)

      let width = img.width
      let height = img.height
      // 缩放到 maxWidth
      if (width > MAX_WIDTH) {
        height = Math.round(height * MAX_WIDTH / width)
        width = MAX_WIDTH
      }

      const canvas = document.createElement('canvas')
      canvas.width = width
      canvas.height = height
      const ctx = canvas.getContext('2d')
      ctx.drawImage(img, 0, 0, width, height)

      canvas.toBlob(
        (blob) => {
          if (blob) {
            const compressedFile = new File([blob], file.name.replace(/\.\w+$/, '.jpg'), {
              type: 'image/jpeg',
              lastModified: Date.now()
            })
            resolve(compressedFile)
          } else {
            resolve(file) // fallback to original
          }
        },
        'image/jpeg',
        JPEG_QUALITY
      )
    }
    img.onerror = () => {
      URL.revokeObjectURL(url)
      resolve(file) // fallback
    }
    img.src = url
  })
}

function formatSize(bytes: number): string {
  if (bytes < 1024) return bytes + ' B'
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + ' KB'
  return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
}

const onFileChange = async (e: Event) => {
  const target = e.target as HTMLInputElement
  const file = target.files?.[0]
  if (!file) return
  uploading.value = true
  compressed.value = false

  const originalSize = file.size

  // 压缩超大图片
  const processedFile = await compressImage(file)
  selectedFile = processedFile

  if (processedFile.size < originalSize) {
    compressed.value = true
    fileSizeInfo.value = `${formatSize(originalSize)} → ${formatSize(processedFile.size)}`
  } else {
    fileSizeInfo.value = formatSize(originalSize)
  }

  // 本地预览（用压缩后的文件）
  const reader = new FileReader()
  reader.onload = (ev) => {
    preview.value = ev.target?.result as string
    uploading.value = false
  }
  reader.readAsDataURL(processedFile)
}

const reset = () => {
  preview.value = null
  selectedFile = null
  fileSizeInfo.value = ''
  compressed.value = false
  uploadError.value = ''
}

const uploadError = ref('')

// 错误消息脱敏：不向用户暴露后端服务名称
function sanitizeError(msg: string | undefined): string {
  if (!msg) return '请稍后重试'
  if (msg.includes('火山引擎') || msg.includes('VolcEngine')) return '图片分析服务暂时不可用，请稍后重试'
  if (msg.includes('MiniMax')) return '语音服务暂时不可用，请稍后重试'
  if (msg.includes('OSS') || msg.includes('oss')) return '文件存储服务异常，请稍后重试'
  if (msg.includes('401') || msg.includes('签名')) return '请返回首页重新开始'
  if (msg.includes('403')) return '认证失败，请检查机构配置后重试'
  if (msg.includes('timeout') || msg.includes('超时')) return '网络超时，请检查网络后重试'
  return msg
}

const goNext = async () => {
  if (!selectedFile) return
  if (!quotaOk.value) {
    uploadError.value = quotaMsg.value || '创作额度不足'
    return
  }
  uploading.value = true
  uploadError.value = ''
  uploadStage.value = 1
  try {
    // Step 1: STS 直传图片到 OSS
    uploadProgress.value = '上传画作到云端...'
    const ossUrl = await ossUpload(selectedFile, {
      type: 'img',
      onProgress: (p: number) => { uploadProgress.value = `上传画作 ${p}%` }
    })
    uploadStage.value = 2

    // Step 2: 用 URL 调 A6 角色提取
    uploadProgress.value = 'AI 正在识别角色...'
    const res = await extractCharacters(ossUrl, {
      saveOriginal: saveOriginal.value
    })
    const data = res || {}
    const chars = (data.characters || []).map((c: any) => ({
      ...c,
      type: c.charType || c.type || 'SIDEKICK'
    }))
    if (chars.length === 0) throw new Error('AI未识别到角色，请更换图片重试')
    store.extractId = data.extractId || ''
    store.characters = chars
    store.imageUrl = ossUrl
    if (data.workId) store.originalWorkId = data.workId
    saveExtractDraft({
      imageUrl: ossUrl,
      extractId: data.extractId || '',
      characters: chars,
      raw: data,
    })
    router.push('/p/create/characters')
  } catch (e: any) {
    uploadError.value = '识别失败：' + sanitizeError(e.message)
    uploading.value = false
    uploadProgress.value = ''
  }
}
</script>

<style lang="scss" scoped>
.upload-page {
  min-height: 100vh;
  background: var(--ai-bg);
  display: flex;
  flex-direction: column;
}

.content {
  flex: 1;
  padding: 16px 20px;
  display: flex;
  flex-direction: column;
}

.upload-area {
  flex: 1;
  min-height: 280px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 2px dashed rgba(99, 102, 241, 0.28);
  border-radius: var(--ai-radius);
  background: rgba(99, 102, 241, 0.03);
  text-align: center;
  padding: 32px;
  transition: all 0.2s;
}

.upload-icon-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 72px;
  height: 72px;
  border-radius: 22px;
  background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(236, 72, 153, 0.1));
  margin-bottom: 18px;
}

.upload-icon {
  font-size: 36px;
  color: var(--ai-primary);
}

.uploading-icon {
  font-size: 56px;
  color: var(--ai-primary);
  animation: pulse 1.5s infinite;
}

.upload-title {
  font-size: 17px;
  font-weight: 700;
  color: var(--ai-text);
}

.upload-desc {
  font-size: 13px;
  color: var(--ai-text-sub);
  margin-top: 8px;
  line-height: 1.6;
}

.uploading-text {
  font-size: 16px;
  font-weight: 600;
  margin-top: 16px;
  color: var(--ai-text);
}

.progress-bar {
  width: 200px;
  height: 6px;
  background: var(--ai-border);
  border-radius: 3px;
  margin-top: 16px;
  overflow: hidden;
}

.progress-fill {
  width: 100%;
  height: 100%;
  background: var(--ai-gradient);
  border-radius: 3px;
  animation: loading 1.5s ease-in-out infinite;
}

@keyframes loading {
  0% {
    transform: translateX(-100%)
  }

  100% {
    transform: translateX(200%)
  }
}

.action-btns {
  display: flex;
  gap: 12px;
  margin-top: 20px;
}

.action-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  border-radius: var(--ai-radius);
  padding: 18px 0;
  text-align: center;
  cursor: pointer;
  transition: all 0.2s;

  &.camera {
    background: var(--ai-gradient);
    color: #fff;
    box-shadow: 0 8px 22px rgba(99, 102, 241, 0.32);

    .action-icon {
      color: #fff;
    }

    .action-label {
      color: #fff;
    }

    &:hover {
      transform: translateY(-2px);
      box-shadow: 0 10px 26px rgba(99, 102, 241, 0.38);
    }

    &:active {
      transform: scale(0.98);
    }
  }

  &.album {
    background: #fff;
    border: 1.5px solid rgba(99, 102, 241, 0.22);
    box-shadow: 0 2px 10px rgba(99, 102, 241, 0.06);

    .action-icon {
      color: var(--ai-primary);
    }

    .action-label {
      color: var(--ai-primary);
    }

    &:hover {
      transform: translateY(-2px);
      border-color: var(--ai-primary);
      box-shadow: 0 6px 18px rgba(99, 102, 241, 0.14);
    }

    &:active {
      transform: scale(0.98);
    }
  }
}

.action-icon {
  font-size: 26px;
}

.action-label {
  font-size: 15px;
  font-weight: 700;
}

.preview-card {
  overflow: hidden;
  flex: 1;
}

.preview-image {
  width: 100%;
  aspect-ratio: 4/3;
  background: #f1f0f7;

  img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

.preview-info {
  padding: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.preview-ok-icon {
  font-size: 18px;
  color: var(--ai-success);
}

.preview-ok-text {
  font-size: 14px;
  font-weight: 600;
  color: var(--ai-success);
}

/* 识别中 */
.recognizing-box {
  background: linear-gradient(135deg, rgba(99, 102, 241, 0.06), rgba(236, 72, 153, 0.04));
  border-radius: 0 0 var(--ai-radius) var(--ai-radius);
  padding: 24px 16px;
  text-align: center;
}

.recognizing-spinner {
  font-size: 32px;
  color: var(--ai-primary);
  margin-bottom: 12px;
}

.recognizing-text {
  font-size: 14px;
  font-weight: 600;
  color: var(--ai-primary);
}

// 等待内容
.waiting-content {
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
}

.waiting-card {
  background: #fff;
  border: 1px solid rgba(99, 102, 241, 0.06);
  border-radius: var(--ai-radius);
  padding: 18px 20px;
  box-shadow: 0 4px 16px rgba(99, 102, 241, 0.06);
}

.waiting-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--ai-text);
  margin-bottom: 14px;
}

.waiting-steps {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.w-step {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-radius: 12px;
  background: rgba(99, 102, 241, 0.04);
  font-size: 14px;
  color: var(--ai-text-sub);
  transition: all 0.3s;

  &.active {
    background: rgba(99, 102, 241, 0.1);
    color: var(--ai-text);
    font-weight: 600;
  }
}

.w-icon {
  font-size: 18px;
  color: var(--ai-primary);
}

.w-done {
  margin-left: auto;
  color: var(--ai-success);
  font-size: 16px;
}

.waiting-funfact {
  display: flex;
  align-items: center;
  gap: 10px;
  background: linear-gradient(135deg, rgba(167, 139, 250, 0.1), rgba(236, 72, 153, 0.06));
  border: 1px solid rgba(99, 102, 241, 0.08);
  border-radius: var(--ai-radius-sm);
  padding: 14px 16px;
}

.ff-icon {
  font-size: 18px;
  flex-shrink: 0;
  color: #8b5cf6;
}

.ff-text {
  font-size: 13px;
  color: var(--ai-text);
  line-height: 1.6;
}

.quota-warn {
  background: rgba(245, 158, 11, 0.1);
  color: #b45309;
  font-size: 13px;
  text-align: center;
  padding: 10px 16px;
  border-radius: 10px;
  margin-top: 12px;
  font-weight: 600;
}

.upload-error {
  color: #ef4444;
  font-size: 13px;
  text-align: center;
  margin-top: 12px;
  font-weight: 500;
}

.preview-actions {
  display: flex;
  gap: 12px;
  margin-top: 12px;

  button {
    flex: 1;
  }
}

.preview-next-btn {
  display: flex !important;
  align-items: center;
  justify-content: center;
  gap: 8px;

  :deep(.anticon) {
    font-size: 16px;
  }
}
</style>
