<template>
  <!-- 登录页面 — 左右分栏布局，包含登录和简历上传流程 -->
  <div class="login-view">

    <!-- ==================== 左侧视觉动画面板（所有阶段共用） ==================== -->
    <section class="login-visual" ref="loginVisualRef" aria-label="OfferPai AI job platform">
      <div class="ai-hub-stage" ref="stageRef">
        <!-- 标题 -->
        <div class="hero-title" aria-hidden="true">
          AI 驱动的-
          <span class="hero-title__second-line">一站式求职辅助平台</span>
        </div>
        <!-- Nova 机器人核心（眼睛跟踪鼠标） -->
        <div class="nova-core" ref="novaCoreRef" aria-hidden="true">
          <img class="nova-core-base" :src="novaCoreSrc" alt="" draggable="false" />
          <img class="nova-eye nova-eye-left" ref="novaEyeLeftRef" :src="novaEyeLeftSrc" alt="" draggable="false" />
          <img class="nova-eye nova-eye-right" ref="novaEyeRightRef" :src="novaEyeRightSrc" alt="" draggable="false" />
        </div>
      </div>
      <!-- 底部 logo -->
      <img class="login-jianshi-logo" :src="jianshiLogoSrc" alt="" aria-hidden="true" draggable="false" />
    </section>

    <!-- ==================== 右侧面板 ==================== -->
    <div class="login-view__right">

      <!-- ===== 登录阶段（step 1 & 2）===== -->
      <template v-if="step === 1 || step === 2">
        <div class="login-view__logo-row login-view__logo-row--right">
          <img src="@/assets/images/logo.png" alt="">
        </div>

        <div class="login-view__form-wrap">
          <!-- 步骤一：输入手机号发送验证码 -->
          <template v-if="step === 1">
            <div class="login-view__heading">
              <h2 class="login-view__title">手机号登录/注册</h2>
              <p class="login-view__subtitle">未注册用户验证后将自动注册并登录</p>
            </div>
            <!-- 手机号输入组 -->
            <div class="login-view__phone-row">
              <!-- 手机图标 -->
              <div class="login-view__phone-icon">
                <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <rect x="4.5" y="2" width="9" height="14" rx="1.5" stroke="#0F6B66" stroke-width="1.5"/>
                  <line x1="9" y1="2" x2="9" y2="3.5" stroke="#0F6B66" stroke-width="1.5" stroke-linecap="round"/>
                </svg>
              </div>
              <!-- 国家码标签 -->
              <span class="login-view__country-label">中国 +86</span>
              <!-- 下拉箭头 -->
              <div class="login-view__country-chevron">
                <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path d="M6 8L10 12L14 8" stroke="#6F8586" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
                </svg>
              </div>
              <!-- 分隔线 -->
              <div class="login-view__input-divider"></div>
              <!-- 手机号输入框 -->
              <input v-model="phone" type="tel" class="login-view__phone-input" placeholder="请输入手机号码" maxlength="11" />
            </div>
            <!-- 发送验证码按钮 -->
            <button class="login-view__send-btn" :disabled="!canSend" @click="handleSendCode">发送验证码</button>
          </template>

          <!-- 步骤二：输入验证码登录 -->
          <template v-if="step === 2">
            <div class="login-view__heading">
              <h2 class="login-view__title">请输入验证码</h2>
              <p class="login-view__subtitle dflex"><span class="dib">短信验证码已发送至 +86{{ phone }} </span><span class="fw600 ml5 dib cursor-po mlauto" @click="handleChangePhone">修改手机号</span></p>
            </div>
            <div class="login-view__otp-wrap" @click="focusOtpInput">
              <input ref="otpInputRef" v-model="otpValue" type="text" inputmode="numeric" class="login-view__otp-hidden-input" maxlength="6" :disabled="isLoggingIn" @input="handleOtpInput" />
              <div v-for="(_, idx) in 6" :key="idx" class="login-view__otp-box" :class="{ 'login-view__otp-box--active': idx === otpValue.length && !isLoggingIn, 'login-view__otp-box--filled': idx < otpValue.length }">
                <span v-if="idx < otpValue.length" class="login-view__otp-digit">{{ otpValue[idx] }}</span>
                <span v-else-if="idx === otpValue.length && !isLoggingIn" class="login-view__otp-cursor"></span>
              </div>
            </div>
            <p v-if="smsStatusMsg" class="login-view__sms-status" :class="{ 'login-view__sms-status--error': smsStatusIsError }">{{ smsStatusMsg }}</p>
            <button class="login-view__status-btn" :disabled="isLoggingIn || countdown > 0" @click="handleResendCode">
              <template v-if="isLoggingIn"><span class="login-view__spinner"></span><span>登录中</span></template>
              <template v-else-if="countdown > 0">{{ countdown }} 秒后可继续发送</template>
              <template v-else>再次发送验证码</template>
            </button>
          </template>

          <!-- 协议勾选（步骤一二通用） -->
          <div class="login-view__agreement">
            <div class="login-view__checkbox" :class="{ 'login-view__checkbox--checked': agreedTerms }" @click="agreedTerms = !agreedTerms">
              <span v-if="agreedTerms" class="login-view__check-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="0.13rem" height="0.13rem"><path stroke="currentColor" fill="currentColor"  d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496"></path></svg></span>
            </div>
            <span class="login-view__agreement-text">登录即表示同意</span>
            <span class="login-view__agreement-link" @click="openAgreement('ae8065i3')">《用户协议》</span>
            <span class="login-view__agreement-text">与</span>
            <span class="login-view__agreement-link" @click="openAgreement('hf8375i8')">《隐私政策》</span>
          </div>
        </div>
      </template>

      <!-- ===== 简历上传阶段（step 3）===== -->
      <template v-if="step === 3">
        <div class="login-view__form-wrap login-view__form-wrap--resume">

          <!-- 状态1：上传区域 upload-->
          <template v-if="resumeState === 'upload'">
            <div class="login-view__heading">
              <h2 class="login-view__title">上传简历，开启求职之旅</h2>
              <p class="login-view__subtitle">解析后为你匹配岗位，也会用于后续简历优化</p>
            </div>
            <!-- 拖拽上传区域 -->
            <div class="login-view__upload-area" @dragover.prevent @drop.prevent="handleDrop" @click="triggerFileInput">
              <div class="login-view__upload-circle">
                <svg width="52" height="56" viewBox="0 0 52 56" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <rect x="0.5" y="0.5" width="51" height="55" rx="7.5" fill="#EAF8F7" stroke="#D6F1EF"/>
                  <rect x="36" width="16" height="16" rx="3" fill="#D6F1EF"/>
                  <rect x="13" y="21" width="26" height="4" rx="2" fill="#4FC2C9"/>
                  <rect x="13" y="33" width="30" height="4" rx="2" fill="#BFEAE8"/>
                  <rect x="13" y="45" width="20" height="4" rx="2" fill="#BFEAE8"/>
                </svg>

              </div>
              <p class="login-view__upload-text">拖拽简历到这里，或点击上传</p>
              <p class="login-view__upload-hint">支持 PDF / DOC / DOCX，不超过 20MB</p>
            </div>
            <!-- 隐藏的文件选择器 -->
            <input ref="fileInputRef" type="file" accept=".pdf,.doc,.docx" class="login-view__file-input" @change="handleFileChange" />
            <!-- 上传按钮 -->
            <button class="login-view__upload-btn" @click="triggerFileInput">+ 上传简历</button>
<!--            <div class="tac"><span class="color-middle cursor-po fs14" @click="handleEnterDashboard">跳过，稍后填写</span></div>-->
          </template>

          <!-- 状态2：解析中 parsing -->
          <template v-if="resumeState === 'parsing'">
            <div class="login-view__heading m0auto">
              <h2 class="login-view__title">正在解析简历</h2>
              <p class="login-view__subtitle">正在识别教育背景、工作经历与技能关键词</p>
            </div>
            <!-- 解析面板 -->
            <div class="login-view__parse-panel m0auto">
              <!-- 顶部进度条 -->
              <div class="login-view__parse-progress-header">
                <span class="login-view__parse-progress-label">解析进度</span>
                <span class="login-view__parse-progress-percent">{{ parsePercent }}%</span>
              </div>
              <div class="login-view__parse-progress-rail">
                <div class="login-view__parse-progress-fill" :style="{ width: parsePercent + '%' }"></div>
              </div>
              <!-- 步骤列表 -->
              <div class="login-view__parse-steps">
                <div v-for="(ps, idx) in parseStepList" :key="idx" class="login-view__parse-step-item">
                  <!-- 步骤圆圈 + 连接线 -->
                  <div class="login-view__parse-step-left">
                    <!-- 完成状态：实心深色圆 -->
                    <div v-if="ps.status === 'done'" class="login-view__parse-dot login-view__parse-dot--done"></div>
                    <!-- 进行中：浅色光晕 + 呼吸动画深色内圆 -->
                    <div v-else-if="ps.status === 'active'" class="login-view__parse-dot login-view__parse-dot--active">
                      <div class="login-view__parse-dot-pulse"></div>
                    </div>
                    <!-- 等待状态：浅色圆 -->
                    <div v-else class="login-view__parse-dot login-view__parse-dot--pending"></div>
                    <!-- 连接线（最后一项不显示） -->
                    <div v-if="idx < parseStepList.length - 1" class="login-view__parse-connector"></div>
                  </div>
                  <!-- 步骤文字 -->
                  <span class="login-view__parse-step-text" :class="{ 'login-view__parse-step-text--pending': ps.status === 'pending' }">{{ ps.label }}</span>
                  <!-- 步骤状态文字 -->
                  <span class="login-view__parse-step-state" :class="{ 'login-view__parse-step-state--done': ps.status === 'done', 'login-view__parse-step-state--active': ps.status === 'active', 'login-view__parse-step-state--pending': ps.status === 'pending' }">{{ ps.stateText }}</span>
                </div>
              </div>
            </div>
            <!-- 解析中按钮（禁用状态） -->
            <button class="login-view__parse-btn m0auto" disabled>解析中，请稍候</button>
          </template>

          <!-- 状态4：上传成功 success-->
          <template v-if="resumeState === 'success'">
            <div class="login-view__heading m0auto">
              <h2 class="login-view__title">简历上传成功</h2>
              <p class="login-view__subtitle">简历已完成解析，进入后台继续完善与投递</p>
            </div>
            <!-- 成功面板 -->
            <div class="login-view__success-panel m0auto">
              <!-- 成功图标和标题 -->
              <div class="login-view__success-header">
                <div class="login-view__success-icon-bg">
                  <span class="login-view__success-icon-mark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="0.15rem" height="0.15rem"><path stroke="currentColor" fill="currentColor"  d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496"></path></svg></span>
                </div>
                <div class="login-view__success-header-text">
                  <span class="login-view__success-title">简历解析完成</span>
                  <span class="login-view__success-detail">已识别关键信息，岗位匹配已准备好</span>
                </div>
              </div>
              <!-- 成功提示项 -->
              <div class="login-view__success-tips">
                <div class="login-view__success-tip-item">
                  <span class="login-view__success-tip-dot"></span>
                  <span class="login-view__success-tip-text">已提取教育背景、工作经历与技能</span>
                </div>
                <div class="login-view__success-tip-item">
                  <span class="login-view__success-tip-dot"></span>
                  <span class="login-view__success-tip-text">可进入后台查看推荐岗位并继续投递</span>
                </div>
              </div>
            </div>
            <!-- 进入后台按钮 -->
            <button class="login-view__success-btn m0auto" @click="handleEnterDashboard">上传成功，进入后台</button>
          </template>

          <!-- 状态3：上传失败 failed-->
          <template v-if="resumeState === 'failed'">
            <div class="login-view__heading m0auto">
              <h2 class="login-view__title">简历上传失败</h2>
              <p class="login-view__subtitle">文件没有解析成功，请检查格式后重新上传
<!--                或 <span class="color-accent cursor-po" @click="handleEnterDashboard">跳过，稍后填写</span>-->
              </p>
            </div>
            <!-- 失败面板 -->
            <div class="login-view__failed-panel">
              <!-- 失败图标和标题 -->
              <div class="login-view__failed-header">
                <div class="login-view__failed-icon-bg">
                  <span class="login-view__failed-icon-mark">!</span>
                </div>
                <div class="login-view__failed-header-text">
                  <span class="login-view__failed-title">未能读取文件内容</span>
                  <span class="login-view__failed-detail">可能是文件加密、损坏或格式不支持</span>
                </div>
              </div>
              <!-- 失败提示项 -->
              <div class="login-view__failed-tips">
                <div class="login-view__failed-tip-item">
                  <span class="login-view__failed-tip-dot"></span>
                  <span class="login-view__failed-tip-text">请上传 PDF / DOC / DOCX 格式</span>
                </div>
                <div class="login-view__failed-tip-item">
                  <span class="login-view__failed-tip-dot"></span>
                  <span class="login-view__failed-tip-text">文件大小不超过 20MB，且未加密</span>
                </div>
              </div>
            </div>
            <!-- 重新上传按钮 -->
            <button class="login-view__failed-btn" @click="handleRetryUpload">重新上传</button>
          </template>

        </div>
      </template>
      <a class="login-view__right-footer" href="https://www.jianshixingqiu.com" target="_blank">
        见识星球旗下 · AI 求职产品
      </a>
    </div>

    <!-- 协议预览弹窗 -->
    <AgreementPreviewDialog v-model="showAgreementDialog" :code="agreementCode" />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'
import { sendSmsCode, smsLogin } from '@/api/auth'
import { fetchProfile, syncProfileFromResume } from '@/api/profile'
import { uploadResume } from '@/utils/aiRequest'
import { fetchJobIntention, saveJobIntention } from '@/api/jobs'
import AgreementPreviewDialog from '@/components/tools/AgreementPreviewDialog.vue'

// 左侧动画面板图片资源
import novaCoreSrc from '@/assets/images/loging_nodes/nova_core_not_eyes.png'
import novaEyeLeftSrc from '@/assets/images/loging_nodes/nova_eye_left.png'
import novaEyeRightSrc from '@/assets/images/loging_nodes/nova_eye_right.png'
import jianshiLogoSrc from '@/assets/images/loging_nodes/logo_jianshixingqiu.png'
import bgSrc from '@/assets/images/loging_nodes/ai_hub_nodes_bj_figma.png'
// 节点批次图片
import node01 from '@/assets/images/loging_nodes/01_jobs_total_board.png'
import node02 from '@/assets/images/loging_nodes/02_jobs_reference_deck.png'
import node03 from '@/assets/images/loging_nodes/03_jobs_match_radar.png'
import node04 from '@/assets/images/loging_nodes/04_jobs_role_chips.png'
import node05 from '@/assets/images/loging_nodes/05_jobs_daily_update.png'
import node06 from '@/assets/images/loging_nodes/06_delivery_flow_status.png'
import node07 from '@/assets/images/loging_nodes/07_delivery_page_detect.png'
import node08 from '@/assets/images/loging_nodes/08_delivery_task_queue.png'
import node09 from '@/assets/images/loging_nodes/09_delivery_submit_check.png'
import node10 from '@/assets/images/loging_nodes/10_delivery_plugin_online.png'
import node11 from '@/assets/images/loging_nodes/11_mentor_shadow_panel.png'
import node12 from '@/assets/images/loging_nodes/12_mentor_resource_pool.png'
import node13 from '@/assets/images/loging_nodes/13_mentor_field_bars.png'
import node14 from '@/assets/images/loging_nodes/14_mentor_booking_slots.png'
import node15 from '@/assets/images/loging_nodes/15_mentor_review_chip.png'

const store = useStore()
const router = useRouter()
const route = useRoute()


// 声明友盟统计 _czc 对象
const _czc: any[] = (window as any)._czc || [];
(window as any)._czc = _czc;
// 绑定 siteid，请用您的 siteid 替换下方 "XXXXXXXX" 部分
_czc.push(["_setAccount", "1281490571"]);

// ==================== 左侧动画面板 ====================
/** 舞台引用 */
const stageRef = ref<HTMLDivElement | null>(null)
/** 视觉区域引用 */
const loginVisualRef = ref<HTMLElement | null>(null)
/** Nova 核心引用 */
const novaCoreRef = ref<HTMLDivElement | null>(null)
/** 左眼引用 */
const novaEyeLeftRef = ref<HTMLImageElement | null>(null)
/** 右眼引用 */
const novaEyeRightRef = ref<HTMLImageElement | null>(null)

/** 动画时间配置 */
const animTiming = { fadeMs: 1400, holdMs: 3900, stepMs: 5300, lifeMs: 6700 }
/** 波次定时器 */
let waveTimer: ReturnType<typeof setTimeout> | null = null
/** ResizeObserver 实例 */
let resizeObs: ResizeObserver | null = null
/** 眨眼定时器 */
let blinkTimer: ReturnType<typeof setTimeout> | null = null
/** 空闲注视定时器 */
let idleLookTimer: ReturnType<typeof setTimeout> | null = null
/** 指针事件引用（用于卸载时清理） */
let handlePointerMove: (e: PointerEvent) => void = () => {}
let handlePointerLeave: () => void = () => {}

/** 节点批次数据 */
const nodeBatches = [
  [
    { src: node01, x: 53, y: 358, w: 226, h: 134 },
    { src: node02, x: 418, y: 182, w: 236, h: 128 },
    { src: node03, x: 696, y: 397, w: 208, h: 136 },
    { src: node04, x: 263, y: 700, w: 264, h: 116 },
    { src: node05, x: 630, y: 655, w: 214, h: 76 }
  ],
  [
    { src: node06, x: 344, y: 168, w: 306, h: 126 },
    { src: node07, x: 39, y: 478, w: 226, h: 136 },
    { src: node08, x: 618, y: 638, w: 214, h: 166 },
    { src: node09, x: 274, y: 720, w: 240, h: 122 },
    { src: node10, x: 697, y: 443, w: 230, h: 76 }
  ],
  [
    { src: node11, x: 372, y: 191, w: 232, h: 124 },
    { src: node12, x: 45, y: 395, w: 244, h: 140 },
    { src: node13, x: 688, y: 370, w: 214, h: 134 },
    { src: node14, x: 400, y: 707, w: 256, h: 124 },
    { src: node15, x: 688, y: 581, w: 222, h: 84 }
  ]
]

// ==================== 页面步骤控制 ====================
/** 当前步骤：1=输入手机号 2=输入验证码 3=上传简历 */
const step = ref(1)

// ==================== 登录相关状态 ====================
/** 手机号 */
const phone = ref('')
/** 验证码6位值 */
const otpValue = ref('')
/** 是否已勾选协议 */
const agreedTerms = ref(false)
/** 倒计时秒数 */
const countdown = ref(0)
let countdownTimer: ReturnType<typeof setInterval> | null = null
/** 是否正在调用登录接口 */
const isLoggingIn = ref(false)
/** 验证码发送状态提示文字 */
const smsStatusMsg = ref('')
/** 状态提示是否为错误样式 */
const smsStatusIsError = ref(false)
/** OTP 输入框引用 */
const otpInputRef = ref<HTMLInputElement | null>(null)
/** 协议弹窗状态 */
const showAgreementDialog = ref(false)
const agreementCode = ref('')
/** 登录成功后的跳转路径（缓存） */
let loginRedirect = '/'

/** 发送按钮是否可用 */
const canSend = computed(() => phone.value.length === 11 && agreedTerms.value)

// ==================== 简历上传相关状态 ====================
/** 简历上传子状态：upload=上传区域 parsing=解析中 failed=失败 success=成功 */
const resumeState = ref<'upload' | 'parsing' | 'failed' | 'success'>('upload')
/** 解析文字轮换定时器 */
let parsingTimer: ReturnType<typeof setInterval> | null = null
/** 文件选择器引用 */
const fileInputRef = ref<HTMLInputElement | null>(null)

/** 进度条百分比 */
const parsePercent = ref(0)
/** 进度条动画定时器 */
let progressTimer: ReturnType<typeof setInterval> | null = null
/** 接口是否已返回成功 */
let uploadDone = false

/** 解析步骤定义 */
interface ParseStep {
  label: string
  status: 'pending' | 'active' | 'done'
  stateText: string
}

/** 4个解析步骤 */
const parseStepList = ref<ParseStep[]>([
  { label: '教育背景解析...', status: 'pending', stateText: '等待' },
  { label: '工作经历解析...', status: 'pending', stateText: '等待' },
  { label: '技能特长解析...', status: 'pending', stateText: '等待' },
  { label: '个人资料最终确认', status: 'pending', stateText: '等待' }
])

/** 每个步骤的时间分配（毫秒），总模拟时间约8秒 */
const stepDurations = [2000, 2000, 2000, 2000]

// ==================== 登录逻辑 ====================

/** 打开协议预览 */
function openAgreement(code: string) {
  agreementCode.value = code
  showAgreementDialog.value = true
}

/** 聚焦OTP隐藏输入框 */
function focusOtpInput() {
  otpInputRef.value?.focus()
}

/** 修改手机号 — 回到步骤一并清空所有状态 */
function handleChangePhone() {
  step.value = 1
  phone.value = ''
  otpValue.value = ''
  countdown.value = 0
  smsStatusMsg.value = ''
  smsStatusIsError.value = false
  isLoggingIn.value = false
  if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null }
}

/** 开始60秒倒计时 */
function startCountdown() {
  countdown.value = 60
  countdownTimer = setInterval(() => {
    countdown.value--
    if (countdown.value <= 0 && countdownTimer) { clearInterval(countdownTimer); countdownTimer = null }
  }, 1000)
}

/** 步骤一：发送验证码 */
async function handleSendCode() {
  if (!phone.value) { ElMessage.warning('请输入手机号'); return }
  if (!agreedTerms.value) { ElMessage.warning('请先同意用户协议与隐私政策'); return }
  try {
    const res = await sendSmsCode(phone.value)
    if (res.code === '0') {
      smsStatusMsg.value = '验证码发送成功，请输入'
      smsStatusIsError.value = false
      step.value = 2
      startCountdown()
      nextTick(() => focusOtpInput())
    } else if (res.msg && res.msg.includes('请勿重复发送')) {
      smsStatusMsg.value = '验证码还在有效期内，请输入'
      smsStatusIsError.value = false
      step.value = 2
      startCountdown()
      nextTick(() => focusOtpInput())
    } else {
      smsStatusMsg.value = res.msg || '验证码发送失败'
      smsStatusIsError.value = true
    }
  } catch (err: any) {
    const msg = err?.response?.data?.msg || '验证码发送失败'
    smsStatusMsg.value = msg
    smsStatusIsError.value = true
  }
}

/** OTP输入处理 */
function handleOtpInput() {
  otpValue.value = otpValue.value.replace(/\D/g, '').slice(0, 6)
  if (otpValue.value.length === 6) { handleLogin() }
}

/** 步骤二：再次发送验证码 */
async function handleResendCode() {
  if (isLoggingIn.value || countdown.value > 0) return
  try {
    const res = await sendSmsCode(phone.value)
    if (res.code === '0') {
      smsStatusMsg.value = '验证码发送成功，请输入'
      smsStatusIsError.value = false
      otpValue.value = ''
      startCountdown()
      nextTick(() => focusOtpInput())
    } else if (res.msg && res.msg.includes('请勿重复发送')) {
      smsStatusMsg.value = '验证码还在有效期内，请输入'
      smsStatusIsError.value = false
    } else {
      smsStatusMsg.value = res.msg || '验证码发送失败'
      smsStatusIsError.value = true
    }
  } catch (err: any) {
    const msg = err?.response?.data?.msg || '验证码发送失败'
    smsStatusMsg.value = msg
    smsStatusIsError.value = true
  }
}

/** 调用登录接口 */
async function handleLogin() {
  isLoggingIn.value = true
  try {
    const res = await smsLogin(phone.value, otpValue.value)
    if (res.code !== '0') {
      ElMessage.error('登录失败请核对验证码')
      otpValue.value = ''
      isLoggingIn.value = false
      nextTick(() => focusOtpInput())
      return
    }

    // 登录成功
    ElMessage.success(`欢迎回来，${res.data?.nick || ''}`)
    if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null }
    countdown.value = 0
    store.commit('SET_AUTHENTICATED', true)

    // 缓存跳转路径
    loginRedirect = (route.query.redirect as string) || '/'

    // 检测是否需要上传简历
    try {
      const profileRes = await fetchProfile()
      if (profileRes.code === '0' && (!profileRes.data || profileRes.data === null)) {
        // 无个人资料，进入上传简历步骤
        step.value = 3
        resumeState.value = 'upload'
        isLoggingIn.value = false
        return
      }
    } catch {
      // 接口异常不阻塞，直接跳转
    }

    // 有个人资料，直接跳转
    router.replace(loginRedirect)
  } catch {
    ElMessage.error('登录失败请核对验证码')
    otpValue.value = ''
    isLoggingIn.value = false
    nextTick(() => focusOtpInput())
  }
}

// ==================== 简历上传逻辑 ====================

/** 触发文件选择 */
function triggerFileInput() {
  fileInputRef.value?.click()
}

/** 文件选择器 change 事件 */
function handleFileChange(e: Event) {
  const input = e.target as HTMLInputElement
  if (input.files && input.files[0]) {
    startUpload(input.files[0])
  }
}

/** 拖放上传 */
function handleDrop(e: DragEvent) {
  const file = e.dataTransfer?.files[0]
  if (file) { startUpload(file) }
}

/** 重新上传 */
function handleRetryUpload() {
  resumeState.value = 'upload'
}

/** 进入后台（状态4按钮） */
function handleEnterDashboard() {
  router.replace(loginRedirect)
}

/** 开始上传并解析简历 */
async function startUpload(file: File) {
  // 校验文件类型和大小
  const validTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']
  if (!validTypes.includes(file.type)) {
    ElMessage.warning('请上传 PDF / DOC / DOCX 格式文件')
    return
  }
  if (file.size > 20 * 1024 * 1024) {
    ElMessage.warning('文件大小不能超过 20MB')
    return
  }

  // 进入解析状态，重置步骤
  resumeState.value = 'parsing'
  uploadDone = false
  parsePercent.value = 0
  parseStepList.value = [
    { label: '教育背景解析...', status: 'pending', stateText: '等待' },
    { label: '工作经历解析...', status: 'pending', stateText: '等待' },
    { label: '技能特长解析...', status: 'pending', stateText: '等待' },
    { label: '个人资料最终确认', status: 'pending', stateText: '等待' }
  ]
  startParsingAnimation()

  try {
    const res = await uploadResume(file)
    if (res.code === 0 && res.data?.resumeId) {
      // 上传成功，同步个人资料
      await syncProfileFromResume(String(res.data.resumeId))
      // 等待1秒后同步AI推荐岗位到求职意向
      await new Promise(resolve => setTimeout(resolve, 1000))
      const intentionRes = await fetchJobIntention()
      if (intentionRes.code === '0' && intentionRes.data) {
        const intention = intentionRes.data
        // 把 aiCategoryIds 复制到 categoryIds 后保存
        if (intention.aiCategoryIds && intention.aiCategoryIds.length) {
          intention.categoryIds = intention.aiCategoryIds.map(Number)
          await saveJobIntention(intention)
        }
      }
      // 标记接口已返回
      uploadDone = true
      // 完成所有步骤的动画后会自动过渡到 success 状态
      finishParsingAnimation()
    } else {
      // 解析失败
      stopParsingAnimation()
      resumeState.value = 'failed'
    }
  } catch {
    stopParsingAnimation()
    resumeState.value = 'failed'
  }
}

/** 开始解析步骤动画（模拟4步进度） */
function startParsingAnimation() {
  let currentStep = 0
  const totalSteps = parseStepList.value.length
  /** 每个步骤对应的进度百分比段 */
  const stepPercents = [25, 50, 75, 95]

  // 第一个步骤立即进入进行中
  parseStepList.value[0].status = 'active'
  parseStepList.value[0].stateText = '进行中'

  // 开始进度条平滑增长
  let targetPercent = stepPercents[0]
  const startTime = Date.now()
  let lastStepTime = startTime

  progressTimer = setInterval(() => {
    const elapsed = Date.now() - lastStepTime
    const stepDuration = stepDurations[currentStep] || 2000

    if (elapsed >= stepDuration && currentStep < totalSteps - 1) {
      // 当前步骤完成
      parseStepList.value[currentStep].status = 'done'
      parseStepList.value[currentStep].stateText = '完成'
      currentStep++
      // 下一个步骤进入进行中
      parseStepList.value[currentStep].status = 'active'
      parseStepList.value[currentStep].stateText = '进行中'
      targetPercent = stepPercents[currentStep]
      lastStepTime = Date.now()
    } else if (currentStep >= totalSteps - 1 && elapsed >= stepDuration) {
      // 最后一个步骤时间到，但如果接口还没返回就停在这里等
      if (uploadDone) {
        // 接口已返回，最后一步也完成
        parseStepList.value[currentStep].status = 'done'
        parseStepList.value[currentStep].stateText = '完成'
        if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
        // 进度条跳到随机95-99再走完到100
        const finalPercent = Math.floor(95 + Math.random() * 4)
        parsePercent.value = finalPercent
        setTimeout(() => {
          parsePercent.value = 100
          setTimeout(() => { resumeState.value = 'success' }, 600)
        }, 1000)
        return
      }
      // 接口未返回，保持在最后步骤的进行中状态
      parsePercent.value = Math.min(parsePercent.value + 0.1, 94)
      return
    }

    // 平滑增长进度
    const progress = Math.min(elapsed / stepDuration, 1)
    const prevPercent = currentStep > 0 ? stepPercents[currentStep - 1] : 0
    parsePercent.value = Math.floor(prevPercent + (targetPercent - prevPercent) * progress)
  }, 50)
}

/** 接口返回后完成动画 */
function finishParsingAnimation() {
  // 如果进度动画还在跑，标记 uploadDone 即可让 progressTimer 逻辑处理完成
  // 如果动画已经停了（最后一步时间到了等接口），手动完成
  if (!progressTimer) {
    const lastIdx = parseStepList.value.length - 1
    parseStepList.value[lastIdx].status = 'done'
    parseStepList.value[lastIdx].stateText = '完成'
    const finalPercent = Math.floor(95 + Math.random() * 4)
    parsePercent.value = finalPercent
    setTimeout(() => {
      parsePercent.value = 100
      setTimeout(() => { resumeState.value = 'success' }, 600)
    }, 1000)
  }
}

/** 停止解析动画（失败时调用） */
function stopParsingAnimation() {
  if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
  if (parsingTimer) { clearInterval(parsingTimer); parsingTimer = null }
}

/** 组件卸载时清理定时器 */
onBeforeUnmount(() => {
  if (countdownTimer) { clearInterval(countdownTimer) }
  if (parsingTimer) { clearInterval(parsingTimer) }
  if (progressTimer) { clearInterval(progressTimer) }
  if (waveTimer) { clearTimeout(waveTimer); waveTimer = null }
  if (blinkTimer) { clearTimeout(blinkTimer); blinkTimer = null }
  if (idleLookTimer) { clearTimeout(idleLookTimer); idleLookTimer = null }
  if (resizeObs) { resizeObs.disconnect(); resizeObs = null }
  window.removeEventListener('pointermove', handlePointerMove)
  window.removeEventListener('pointerleave', handlePointerLeave)
  document.removeEventListener('visibilitychange', handleVisibilityChange)
})

// ==================== 左侧动画面板逻辑 ====================

/** 工具函数 — 随机数 */
function random(min: number, max: number) { return min + Math.random() * (max - min) }

/** 适配舞台缩放，使其充满视觉区域 */
function fitVisualStage() {
  const stage = stageRef.value
  const visual = loginVisualRef.value
  if (!stage || !visual) return
  const baseWidth = 1100
  const baseHeight = 900
  const rect = visual.getBoundingClientRect()
  const scale = Math.min(rect.width / baseWidth, rect.height / baseHeight)
  const offsetX = (rect.width - baseWidth * scale) / 2
  const offsetY = (rect.height - baseHeight * scale) / 2
  stage.style.setProperty('--stage-scale', scale.toFixed(5))
  // translate 使用 rem（÷100）
  stage.style.setProperty('--stage-x', `${(offsetX / 100).toFixed(4)}rem`)
  stage.style.setProperty('--stage-y', `${(offsetY / 100).toFixed(4)}rem`)
}

/** 创建一个浮动节点面板并播放动画 */
function createPanel(spec: { src: string; x: number; y: number; w: number; h: number }) {
  const stage = stageRef.value
  if (!stage || document.hidden) return
  const img = document.createElement('img')
  img.className = 'node'
  img.src = spec.src
  img.alt = ''
  img.draggable = false
  // 使用 rem 单位（1rem = 100px）
  img.style.setProperty('--x', `${(spec.x / 100).toFixed(4)}rem`)
  img.style.setProperty('--y', `${(spec.y / 100).toFixed(4)}rem`)
  img.style.setProperty('--w', `${(spec.w / 100).toFixed(4)}rem`)
  img.style.setProperty('--h', `${(spec.h / 100).toFixed(4)}rem`)
  img.style.setProperty('--life-ms', `${animTiming.lifeMs}ms`)

  let fallbackTimer: ReturnType<typeof setTimeout> | null = null
  const removePanel = () => {
    if (fallbackTimer) { clearTimeout(fallbackTimer); fallbackTimer = null }
    img.remove()
  }
  img.addEventListener('animationend', removePanel, { once: true })
  stage.appendChild(img)
  fallbackTimer = setTimeout(removePanel, animTiming.lifeMs + 500)
  requestAnimationFrame(() => {
    if (img.isConnected && !document.hidden) img.classList.add('is-running')
  })
}

/** 移除所有活动节点 */
function removeActiveNodes() {
  stageRef.value?.querySelectorAll('.node').forEach(n => n.remove())
}

/** 开始批次节点循环动画 */
function startNodeBatches() {
  let batchIndex = 0
  function spawnAndSchedule() {
    if (!document.hidden) {
      nodeBatches[batchIndex].forEach(createPanel)
      batchIndex = (batchIndex + 1) % nodeBatches.length
    }
    if (waveTimer) clearTimeout(waveTimer)
    waveTimer = setTimeout(() => { waveTimer = null; if (!document.hidden) spawnAndSchedule() }, animTiming.stepMs)
  }
  spawnAndSchedule()
}

/** 眼睛跟踪指针逻辑 */
function setupNovaEyeTracking() {
  const core = novaCoreRef.value
  const leftEye = novaEyeLeftRef.value
  const rightEye = novaEyeRightRef.value
  if (!core || !leftEye || !rightEye) return

  const config = { maxH: 0.72, maxV: 0.27, deadZone: 0.025, trackSmooth: 0.14, idleSmooth: 0.055 }
  const current = { lx: 0, ly: 0, rx: 0, ry: 0 }
  const target = { lx: 0, ly: 0, rx: 0, ry: 0 }
  let animating = false
  let pointerInside = false
  let eyeSmoothing = config.idleSmooth
  let lastIdleAngle: number | null = null

  function calcOffset(px: number, py: number, eye: HTMLElement) {
    const eyeRect = eye.getBoundingClientRect()
    const coreRect = core!.getBoundingClientRect()
    const dx = px - (eyeRect.left + eyeRect.width / 2)
    const dy = py - (eyeRect.top + eyeRect.height / 2)
    const dist = Math.hypot(dx, dy)
    const refSize = Math.max(coreRect.width, coreRect.height) * 0.35
    const norm = refSize > 0 ? dist / refSize : 0
    if (norm < config.deadZone) return { x: 0, y: 0 }
    const angle = Math.atan2(dy, dx)
    const t = Math.min((norm - config.deadZone) / (1 - config.deadZone), 1)
    const factor = t * t * (3 - 2 * t)
    return { x: Math.cos(angle) * eye.offsetWidth * config.maxH * factor, y: Math.sin(angle) * eye.offsetHeight * config.maxV * factor }
  }

  function applyTransforms() {
    current.lx += (target.lx - current.lx) * eyeSmoothing
    current.ly += (target.ly - current.ly) * eyeSmoothing
    current.rx += (target.rx - current.rx) * eyeSmoothing
    current.ry += (target.ry - current.ry) * eyeSmoothing
    leftEye!.style.transform = `translate3d(${current.lx.toFixed(2)}px, ${current.ly.toFixed(2)}px, 0)`
    rightEye!.style.transform = `translate3d(${current.rx.toFixed(2)}px, ${current.ry.toFixed(2)}px, 0)`
    const delta = Math.abs(target.lx - current.lx) + Math.abs(target.ly - current.ly) + Math.abs(target.rx - current.rx) + Math.abs(target.ry - current.ry)
    if (delta > 0.01) { requestAnimationFrame(applyTransforms) } else { animating = false }
  }

  function setTargets(lx: number, ly: number, rx = lx, ry = ly) {
    target.lx = lx; target.ly = ly; target.rx = rx; target.ry = ry
    if (!animating) { animating = true; requestAnimationFrame(applyTransforms) }
  }

  function blink() {
    leftEye!.classList.add('is-blinking')
    rightEye!.classList.add('is-blinking')
    setTimeout(() => { leftEye!.classList.remove('is-blinking'); rightEye!.classList.remove('is-blinking') }, 200)
  }

  function setIdleLook() {
    eyeSmoothing = config.idleSmooth
    const maxX = Math.min(leftEye!.offsetWidth, rightEye!.offsetWidth) * 0.43
    const maxY = Math.min(leftEye!.offsetHeight, rightEye!.offsetHeight) * 0.18
    let angle = random(0, Math.PI * 2)
    if (lastIdleAngle !== null) {
      for (let i = 0; i < 8; i++) {
        const diff = Math.abs(Math.atan2(Math.sin(angle - lastIdleAngle), Math.cos(angle - lastIdleAngle)))
        if (diff > 0.72) break
        angle = random(0, Math.PI * 2)
      }
    }
    lastIdleAngle = angle
    const x = Math.cos(angle) * maxX * random(0.92, 1)
    const y = Math.sin(angle) * maxY * random(0.92, 1)
    setTargets(x, y)
  }

  function scheduleIdleLook(delay = random(900, 2300)) {
    if (idleLookTimer) clearTimeout(idleLookTimer)
    idleLookTimer = setTimeout(() => {
      idleLookTimer = null
      if (!pointerInside && !document.hidden) {
        setIdleLook()
        if (Math.random() < 0.34) blink()
        scheduleIdleLook(random(1400, 2200))
      }
    }, delay)
  }

  function scheduleBlink() {
    blinkTimer = setTimeout(() => { blink(); scheduleBlink() }, 2400 + Math.random() * 4600)
  }

  // 暴露给外部事件处理
  handlePointerMove = (e: PointerEvent) => {
    const visual = loginVisualRef.value
    if (!visual) return
    const rect = visual.getBoundingClientRect()
    const inside = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom
    if (inside) {
      pointerInside = true
      eyeSmoothing = config.trackSmooth
      if (idleLookTimer) { clearTimeout(idleLookTimer); idleLookTimer = null }
      const lo = calcOffset(e.clientX, e.clientY, leftEye!)
      const ro = calcOffset(e.clientX, e.clientY, rightEye!)
      setTargets(lo.x, lo.y, ro.x, ro.y)
    } else if (pointerInside) {
      pointerInside = false
      eyeSmoothing = config.idleSmooth
      setTargets(0, 0)
      scheduleIdleLook(650)
    }
  }

  handlePointerLeave = () => {
    pointerInside = false
    eyeSmoothing = config.idleSmooth
    setTargets(0, 0)
    scheduleIdleLook(650)
  }

  window.addEventListener('pointermove', handlePointerMove, { passive: true } as any)
  window.addEventListener('pointerleave', handlePointerLeave, { passive: true } as any)
  scheduleBlink()
  scheduleIdleLook(850)
}

/** 页面可见性切换处理 */
function handleVisibilityChange() {
  if (document.hidden) {
    if (waveTimer) { clearTimeout(waveTimer); waveTimer = null }
    removeActiveNodes()
  } else {
    removeActiveNodes()
    startNodeBatches()
  }
}

/** 挂载时初始化动画 */
onMounted(() => {
  // 设置背景图
  if (stageRef.value) {
    stageRef.value.style.backgroundImage = `url(${bgSrc})`
  }
  fitVisualStage()
  // 监听容器尺寸变化
  if (loginVisualRef.value && 'ResizeObserver' in window) {
    resizeObs = new ResizeObserver(fitVisualStage)
    resizeObs.observe(loginVisualRef.value)
  }
  // 启动眼睛跟踪
  setupNovaEyeTracking()
  // 启动节点动画
  startNodeBatches()
  // 监听页面可见性
  document.addEventListener('visibilitychange', handleVisibilityChange)
})
</script>
