<template>
  <div class="step3">
    <div class="top">
      <div class="l"></div>
      <div class="title">
        <img src="@/assets/step1/title.png" alt="">
      </div>
      <div class="r" @click="onCloseClick">
        <img src="@/assets/close.png" alt="">
      </div>
    </div>
    <div class="main">
      <video class="close-video" :src="closeVideoSrc" :poster="closeVideoPosterSrc" playsinline autoplay loop
        preload="auto" @loadeddata="closeVideoReady = true" @error="closeVideoReady = false" />
      <div class="tips">
        <img class="tips-icon" src="@/assets/step3/loading.png" alt="">{{ tipsText }}
      </div>
    </div>

  </div>
</template>

<script setup lang="ts">
import { Capacitor } from '@capacitor/core';
import { registerPlugin } from '@capacitor/core';
import { ref, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { CameraPreview } from '@capgo/camera-preview';
import closeVideoUrl from '@/assets/step3/3.mp4?url';
import closePosterUrl from '@/assets/step3/3.jpg?url';
const tipsText = ref('正在分析您的皮肤状态...');
const showHelp = ref(true);
const router = useRouter();
const closeVideoSrc = closeVideoUrl;
const closeVideoPosterSrc = closePosterUrl;
const closeVideoReady = ref(false);

const VideoCompress = Capacitor.isNativePlatform()
  ? registerPlugin<any>('VideoCompress')
  : null;
onMounted(() => {
  setTimeout(() => {
    tipsText.value = '正在分析您的衣着及面部微表情....';
    setTimeout(() => {
      tipsText.value = '正在分析您的面色....';
      setTimeout(() => {
        tipsText.value = '正在为您生成报告....';
      }, 2000);
    }, 2000);
  }, 2000);
});
const errorMessage = ref('');
const ARK_API_URL =
  (import.meta.env.VITE_ARK_API_URL as string | undefined) ||
  'https://ark.cn-shanghai.volces.com/api/v3/responses';
const ARK_API_KEY =
  (import.meta.env.VITE_ARK_API_KEY as string | undefined) ||
  '3496e327-0454-426c-8e69-13e905a1e756';

function createAbortError() {
  // 统一用和 fetch abort 一致的错误形态
  const err = new Error('Aborted');
  (err as any).name = 'AbortError';
  return err;
}

async function videoUrlToBlob(videoUrl: string, signal?: AbortSignal): Promise<Blob> {
  const src = Capacitor.convertFileSrc(videoUrl.trim());
  const res = await fetch(src, signal ? { signal } : undefined);
  if (!res.ok) {
    throw new Error(`读取本地视频失败: ${res.status}`);
  }
  return res.blob();
}

async function blobToDataUrl(blob: Blob, signal?: AbortSignal): Promise<string> {
  return await new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    let settled = false;
    const cleanup = () => {
      if (signal) signal.removeEventListener('abort', onAbort);
    };
    const onAbort = () => {
      if (settled) return;
      settled = true;
      cleanup();
      try {
        reader.abort();
      } catch {
        /* ignore */
      }
      reject(createAbortError());
    };
    if (signal) {
      if (signal.aborted) return onAbort();
      signal.addEventListener('abort', onAbort, { once: true });
    }
    reader.onerror = () => {
      if (settled) return;
      settled = true;
      cleanup();
      reject(new Error('读取视频失败'));
    };
    reader.onload = () => {
      if (settled) return;
      settled = true;
      cleanup();
      resolve(String(reader.result || ''));
    };
    reader.readAsDataURL(blob);
  });
}

const pipelineAbortController = ref<AbortController | null>(null);
const isLeaving = ref(false);

function cancelInFlightRequest() {
  const c = pipelineAbortController.value;
  if (c) {
    try {
      c.abort();
    } catch {
      /* ignore */
    }
  }
  pipelineAbortController.value = null;
}

function onCloseClick() {
  isLeaving.value = true;
  cancelInFlightRequest();
  router.replace('/');
}

async function analyzeVideoWithArk(videoDataUrl: string, signal?: AbortSignal) {
  if (!ARK_API_KEY) throw new Error('未设置 VITE_ARK_API_KEY');

  const requestBody: any = {
    model: 'doubao-seed-2-0-mini-260215',
    input: [
      {
        role: 'user',
        content: [
          { type: 'input_video', video_url: videoDataUrl },
          {
            type: 'input_text',
            text: `角色设定
            你是一位基于多模态感知（计算机视觉 + 音频分析）的医疗级 AI 健康顾问。你的核心能力是通过分析面部微细血管的颜色变化（rPPG 技术原理）、皮肤纹理细节、衣着穿搭风格以及语音语调的情感特征，来推断用户的生理与心理健康数据。
            核心原则
            动态场景模拟（关键）：
            拒绝固定剧本： 不要每次都生成一样的数据。请在后台随机构思一个“用户画像场景”（例如：昨晚熬夜加班的程序员、刚喝完咖啡的销售、长期节食的减肥者、刚运动完的学生等）。
            基于场景生成数据： 所有的数值和状态必须服务于你构思的这个场景。
            强制差异化： 严禁直接复制提示词中示例的数值（如心率 101、压力 7.2 等）。必须生成全新的、符合当前模拟场景的数值。
            真实世界的“亚健康”模拟：
            拒绝完美数据： 严禁生成所有指标都完美的“教科书式”数据。
            倾向轻微异常： 真实用户通常处于“亚健康”状态。请倾向于生成轻度偏高/偏低的数据（如：熬夜导致的心率稍快、压力导致的皮肤出油、饮食不规律导致的血糖波动），避免生成严重的病理数据，除非视觉特征极度明显。
            基于证据的生理推断：
            面色潮红/充血 -> 对应：心率/血压轻度偏高、压力、饮酒。
            面色苍白/无血色 -> 对应：血红蛋白偏低、疲劳、低血糖。
            皮肤暗沉/油光重 -> 对应：代谢负担、皮质醇高（压力）、缺水。
            关怀式反馈机制：
            模块级分析： 如果指标异常，analysis 字段必须解释视觉成因，suggestion 字段给出具体改善建议。
            全局总结： summary_text 必须用温暖、关怀的口吻，串联所有异常点，并针对每个问题给出生活化的解决方案。
            分析步骤
            场景构思： 随机模拟一个用户当前的生理状态（如：疲劳、兴奋、压力、缺水）。
            数值生成与状态判定：
            构思数值：模拟真实波动，允许轻度偏离标准值。
            比对范围：确定 status ("正常"、"偏高"、"偏低")。
            注意： skin_status 模块必须包含 analysis（异常分析）和 suggestion（改善建议）两个独立字段。
            报告撰写： 输出 JSON。
            指标参考范围（严格判定标准）
            常规生命体征：心率 (60-100)、呼吸 (12-20)、收缩压 (90-139)、舒张压 (60-90)
            血液健康：血糖 (3.9-6.1)、血红蛋白 (110-165)、甘油三酯 (0.565-1.69)
            心理健康：心理健康 (0-100, 越高越好)、压力 (0-10)、抑郁 (0-10)、焦虑 (0-10)
            皮肤健康：含水量 (10-20%)
            注意：请严格依据上述数值范围判定 status。例如心率 92 在 60-100 范围内，应判定为“正常”。只有当数值超出上述范围时，才标记为“偏高”或“偏低”。
            输出格式
            状态（status）字段只能返回以下三个枚举值之一：'正常'、'偏高'、'偏低'。严禁使用‘正常高值’、‘临界值’、‘轻微异常’等其他描述性词汇。
            请只返回 JSON（不要 Markdown/不要多余文字），格式如下（注意：以下数值仅为格式示例，请务必生成与示例完全不同的新数据）：
            {
            "visual_quality_check": {
            "lighting": "good",
            "face_clarity": "high",
            "signal_reliability": "valid"
            },
            "metrics": {
            "vital_signs": {
            "heart_rate": { "value": 82, "unit": "bpm", "status": "正常", "desc": "心率" },
            "respiratory_rate": { "value": 18, "unit": "rpm", "status": "正常", "desc": "呼吸频率" },
            "systolic_bp": { "value": 128, "unit": "mmHg", "status": "正常", "desc": "收缩压" },
            "diastolic_bp": { "value": 82, "unit": "mmHg", "status": "正常", "desc": "舒张压" },
            "analysis": "面部微血管分布均匀，生命体征平稳。心率处于正常区间，显示心血管系统负荷正常。"
            },
            "blood_health": {
            "glucose": { "value": 5.1, "unit": "mmol/L", "status": "正常", "desc": "血糖" },
            "hemoglobin": { "value": 135, "unit": "g/L", "status": "正常", "desc": "血红蛋白" },
            "triglycerides": { "value": 1.4, "unit": "mmol/L", "status": "正常", "desc": "甘油三酯" },
            "analysis": "唇色红润，面部血色充盈，推测血液携氧能力及代谢指标均在健康区间。"
            },
            "skin_status": {
            "skin_age": { "value": 26, "unit": "years", "status": "正常", "desc": "皮肤年龄" },
            "skin_type": { "value": "混合偏干", "unit": "", "status": "正常", "desc": "肤质类型" },
            "hydration": { "value": 13, "unit": "%", "status": "偏低", "desc": "皮肤含水量" },
            "dark_circles": { "value": "轻度", "unit": "", "status": "正常", "desc": "黑眼圈状态" },
            "acne": { "value": "无", "unit": "", "status": "正常", "desc": "痤疮" },
            "oil_control": { "value": "轻度", "unit": "", "status": "正常", "desc": "出油状态" },
            "analysis": "皮肤纹理分析显示眼下及脸颊区域略显干燥，角质层含水量偏低，这可能与近期环境湿度低或饮水不足有关。",
            "suggestion": "1. 每日饮水量增加至 2000ml；2. 使用含有角鲨烷或维生素 B5 的修复面霜加强保湿。"
            },
            "mental_health": {
            "mental_score": { "value": 88.0, "unit": "score", "status": "正常", "desc": "心理健康指数" },
            "stress": { "value": 3.0, "unit": "score", "status": "正常", "desc": "压力指数" },
            "depression": { "value": 1.5, "unit": "score", "status": "正常", "desc": "抑郁指数" },
            "anxiety": { "value": 2.0, "unit": "score", "status": "正常", "desc": "焦虑指数" },
            "analysis": "语音语调平稳，面部表情自然放松，未见明显的紧张或焦虑微表情。您的心理状态非常健康。"
            }
            },
            "brief_report": {
            "personality": "温和/生活规律",
            "emotion": "平静",
            "clothing_style": "休闲舒适",
            "overall_status": "健康",
            "abnormal_items": [
            "皮肤含水量偏低"
            ],
            "summary_text": "亲爱的用户，很高兴看到您！根据本次多模态检测，您的整体健康状况非常理想。您的心血管系统强健，血液指标正常，且心理状态非常平稳，看来您最近的生活节奏把握得相当不错。唯一的小提示是您的皮肤含水量略低（13%），这可能是因为环境干燥或饮水稍少。建议您：随身携带保温杯，增加饮水频率，并在护肤时多涂抹一层保湿乳液。除此之外，请继续保持您现在的健康生活方式，您做得很好！"
            }
            }`
          },
        ]
      }
    ]
  };

  return await new Promise<any>((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    let settled = false;
    const cleanup = () => {
      if (signal) signal.removeEventListener('abort', onAbort);
    };

    const onAbort = () => {
      if (settled) return;
      settled = true;
      cleanup();
      try {
        xhr.abort();
      } catch {
        /* ignore */
      }
      reject(createAbortError());
    };

    if (signal) {
      if (signal.aborted) return onAbort();
      signal.addEventListener('abort', onAbort, { once: true });
    }

    xhr.open('POST', ARK_API_URL, true);
    xhr.setRequestHeader('Authorization', `Bearer ${ARK_API_KEY}`);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.responseType = 'json';

    xhr.onload = () => {
      if (settled) return;
      settled = true;
      cleanup();
      const ok = xhr.status >= 200 && xhr.status < 300;
      const body = xhr.response ?? null;
      if (!ok) {
        const message = (body as any)?.error?.message || xhr.status;
        reject(new Error(`API请求失败: ${message}`));
        return;
      }
      resolve(body);
    };

    xhr.onerror = () => {
      if (settled) return;
      settled = true;
      cleanup();
      reject(new Error('网络错误'));
    };

    xhr.onabort = () => {
      onAbort();
    };

    xhr.send(JSON.stringify(requestBody));
  });
}

onMounted(async () => {
  // 从 step2 进入后兜底关闭原生相机预览，避免占用摄像头资源
  if (Capacitor.isNativePlatform()) {
    try {
      await CameraPreview.stop({ force: true });
    } catch {
      /* ignore */
    }
  }

  // step2 录制后写入：sessionStorage['step2_video_path']
  let videoPath = sessionStorage.getItem('step2_video_path') || '';
  if (!videoPath) {
    // router.push('/step4');
    return;
  }

  try {
    let pluginVideoInfo: {
      input?: { durationMs?: number; fileSize?: number; uri?: string };
      output?: { durationMs?: number; fileSize?: number; uri?: string };
    } | null = null;

    // Android: 在 step3 统一做压缩/截取，降低 base64 与接口耗时
    if (Capacitor.getPlatform() === 'android' && VideoCompress) {
      try {
        const inputPath = videoPath;
        const ret = await VideoCompress.compressTo4s({
          path: videoPath,
          videoBitrate: 600_000,
          removeAudio: true
        });
        const finalPath = ret?.outputFileUri || ret?.outputPath || videoPath;
        try {

          if (ret?.input || ret?.output) {
            pluginVideoInfo = { input: ret?.input, output: ret?.output };
            const inMs = Number(pluginVideoInfo?.input?.durationMs);
            const inSize = Number(pluginVideoInfo?.input?.fileSize);
            const outMs = Number(pluginVideoInfo?.output?.durationMs);
            const outSize = Number(pluginVideoInfo?.output?.fileSize);
            if (Number.isFinite(inMs)) console.log('[VideoCompress] input_duration_s:', (inMs / 1000).toFixed(2));
            if (Number.isFinite(inSize)) console.log('[VideoCompress] input_size_bytes:', inSize);
            if (Number.isFinite(outMs)) console.log('[VideoCompress] output_duration_s:', (outMs / 1000).toFixed(2));
            if (Number.isFinite(outSize)) console.log('[VideoCompress] output_size_bytes:', outSize);
          }
        } catch {
          /* ignore */
        }
        videoPath = finalPath;
        sessionStorage.setItem('step2_video_path', finalPath);
      } catch (e) {
        console.warn('视频压缩失败，回退使用原视频', e);
      }
    }

    cancelInFlightRequest();
    const controller = new AbortController();
    pipelineAbortController.value = controller;
    const { signal } = controller;

    const now =
      typeof performance !== 'undefined' && typeof performance.now === 'function'
        ? () => performance.now()
        : () => Date.now();
    const t0 = now();

    const tBlob0 = now();
    const blob = await videoUrlToBlob(videoPath, signal);
    const tBlob1 = now();

    const tDataUrl0 = now();
    if (signal.aborted) throw createAbortError();
    const dataUrl = await blobToDataUrl(blob, signal);
    const tDataUrl1 = now();

    const tArk0 = now();
    if (signal.aborted) throw createAbortError();
    const result = await analyzeVideoWithArk(dataUrl, signal);
    const tArk1 = now();
    if (isLeaving.value) return;

    sessionStorage.setItem('step2_ark_result', JSON.stringify(result));
    const tSave = now();

    const timing = {
      total_ms: Math.round(tSave - t0),
      steps_ms: {
        videoUrlToBlob: Math.round(tBlob1 - tBlob0),
        blobToDataUrl: Math.round(tDataUrl1 - tDataUrl0),
        analyzeVideoWithArk: Math.round(tArk1 - tArk0),
        saveResult: Math.round(tSave - tArk1)
      }
    };
    sessionStorage.setItem('step3_timing', JSON.stringify(timing));

    router.push('/step4');
  } catch (e: any) {
    if (isLeaving.value) return;
    if (e?.name === 'AbortError') return;
    console.error(e);
    errorMessage.value = e?.message || '识别失败';
    router.push('/step1');
  }

  // 拿到接口返回（成功/失败都算）再进入 step4

});

onUnmounted(() => {
  cancelInFlightRequest();
});

</script>
<style scoped lang="scss">
.step3 {
  padding-top: 52.97px;

  display: flex;
  flex-direction: column;
  justify-content: space-between;
  // background: url('@/assets/step1/bg.png') no-repeat center center/cover;
  background: #F3F3F3;
  min-height: 100vh;

  .top {
    padding: 0 30px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 50px;

    .l {
      width: 60px;
      height: 60px;
    }

    .r {
      width: 60px;
      height: 60px;

      img {
        width: 100%;
      }
    }

    .title {
      flex: 1;
      text-align: center;

      img {
        width: 340px;
        height: 60px;
        margin: 0 auto;
      }
    }
  }

  .main {
    transform: translateY(-200px);
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    flex: 1;
    flex-direction: column;
    display: flex;
    justify-content: center;
    align-items: center;

    .ip {
      width: 100%;
    }

    .tips {
      display: flex;
      // width: 473px;
      text-align: center;
      height: 67px;
      margin: 100px auto 0;
      font-size: 32px;
      align-items: center;
      justify-content: center;
      gap: 10px;

      .tips-icon {
        width: 32px;
        height: 32px;
        display: inline-block;
        animation: tipsIconSpin 1s linear infinite;
      }
    }

  }


}

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

  to {
    transform: rotate(360deg);
  }
}
</style>
