<!-- =====================================================================
  ViewerApp.vue — 分享查看器（观众版，独立入口）
  数据源：分享 JSON（不连 store），仅静态渲染当前页
  ===================================================================== -->
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'

import type { Deck, Slide } from '../core/types'
import { parseShareHash, shareJsonUrlOf } from '../core/share'
import { resolveBg, registerBgThemeGetter } from '../core/bg'
import { CANVAS_W, CANVAS_H } from '../core/sample'
import ElementView from '../components/editor/ElementView.vue'
import ViewerAnnotations from './ViewerAnnotations.vue'

type Status = 'loading' | 'ready' | 'error' | 'invalid'

const status = ref<Status>('loading')
const errMsg = ref('')
const deck = ref<Deck | null>(null)
const index = ref(0)
const scale = ref(1)
const idle = ref(false)

const slides = computed<Slide[]>(() => deck.value?.slides ?? [])
const total = computed(() => slides.value.length)
const currentSlide = computed<Slide | null>(() => {
  const arr = slides.value
  if (!arr.length) return null
  return arr[Math.max(0, Math.min(index.value, arr.length - 1))]
})

/* ---------- 加载分享 JSON ---------- */
onMounted(async () => {
  const parsed = parseShareHash()
  if (!parsed) {
    status.value = 'invalid'
    return
  }
  try {
    // 优先用链接内嵌的 JSON 地址（观众零配置）；旧格式链接回退本地配置反推
    const jsonUrl = parsed.jsonUrl || shareJsonUrlOf(parsed.shareId)
    const res = await fetch(jsonUrl)
    if (!res.ok) throw new Error('HTTP ' + res.status)
    deck.value = JSON.parse(await res.text()) as Deck
    if (!deck.value?.slides?.length) throw new Error('分享内容为空')
    // 注入 deck 自带主题，让 resolveBg 按分享内容的主题解析背景/元素色
    registerBgThemeGetter(() => String(deck.value?.theme || 'indigo'))
    status.value = 'ready'
    fit()
  } catch (e: any) {
    errMsg.value = e?.message || String(e)
    status.value = 'error'
  }
})

/* ---------- 缩放适配 ---------- */
function fit() {
  scale.value = Math.min(window.innerWidth / CANVAS_W, window.innerHeight / CANVAS_H)
}

/* ---------- 翻页 ---------- */
function next() {
  if (index.value < total.value - 1) index.value++
  wake()
}
function prev() {
  if (index.value > 0) index.value--
  wake()
}

/* ---------- 键盘 ---------- */
function onKey(e: KeyboardEvent) {
  switch (e.key) {
    case 'ArrowRight': case ' ': case 'PageDown':
      e.preventDefault(); next(); break
    case 'ArrowLeft': case 'PageUp':
      e.preventDefault(); prev(); break
  }
}

/* ---------- 点击：左半屏上一页 / 右半屏下一页 ---------- */
function onClick(e: MouseEvent) {
  const stage = stageEl.value
  if (!stage) return
  const rect = stage.getBoundingClientRect()
  if (e.clientX - rect.left > rect.width / 2) next()
  else prev()
}

/* ---------- 触摸滑动 ---------- */
let touchX = 0
function onTouchStart(e: TouchEvent) {
  touchX = e.changedTouches[0].clientX
}
function onTouchEnd(e: TouchEvent) {
  const dx = e.changedTouches[0].clientX - touchX
  if (dx > 40) prev()
  else if (dx < -40) next()
}

/* ---------- 页码 HUD：3 秒无操作淡出 ---------- */
let idleTimer: ReturnType<typeof setTimeout> | null = null
function wake() {
  idle.value = false
  if (idleTimer) clearTimeout(idleTimer)
  idleTimer = setTimeout(() => { idle.value = true }, 3000)
}

function bindEvents() {
  document.addEventListener('keydown', onKey)
  window.addEventListener('resize', fit)
}
function unbindEvents() {
  document.removeEventListener('keydown', onKey)
  window.removeEventListener('resize', fit)
  if (idleTimer) clearTimeout(idleTimer)
}

const stageEl = ref<HTMLElement | null>(null)

onMounted(bindEvents)
onUnmounted(unbindEvents)
</script>

<template>
  <!-- 无效链接 -->
  <div v-if="status === 'invalid'" class="viewer-state">
    <div class="viewer-state-title">链接无效</div>
  </div>

  <!-- 加载失败 -->
  <div v-else-if="status === 'error'" class="viewer-state">
    <div class="viewer-state-title">加载失败，链接可能已失效</div>
    <div v-if="errMsg" class="viewer-state-detail">{{ errMsg }}</div>
  </div>

  <!-- 加载中 -->
  <div v-else-if="status === 'loading'" class="viewer-state">
    <div class="viewer-spinner"></div>
    <div class="viewer-state-title">加载中…</div>
  </div>

  <!-- 渲染 -->
  <div v-else class="viewer-stage" :class="{ idle }" ref="stageEl" @click="onClick" @touchstart.passive="onTouchStart" @touchend.passive="onTouchEnd" @mousemove="wake">
    <!-- 沉浸背景：当前页主色放大铺满 + 模糊，消除黑边（手机竖屏尤甚） -->
    <div class="viewer-backdrop" :style="{ background: resolveBg(currentSlide?.background || 'bg') }"></div>
    <div class="slide-layer" :style="{ transform: 'scale(' + scale + ')' }">
      <div v-if="currentSlide" class="slide-inner" :style="{ background: resolveBg(currentSlide.background) }">
        <ElementView
          v-for="el in currentSlide.elements"
          :key="el.id"
          :el="el"
          :bg="currentSlide.background"
        />
        <ViewerAnnotations :slide="currentSlide" />
      </div>
    </div>
    <div class="viewer-hud">{{ (index + 1) + ' / ' + total }}</div>
  </div>
</template>

<style scoped>
.viewer-state {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  color: var(--text-secondary, #888);
  font-size: 15px;
  user-select: none;
}
.viewer-state-title { font-weight: 500; }
.viewer-state-detail { font-size: 12px; opacity: 0.6; }
.viewer-spinner {
  width: 28px;
  height: 28px;
  border: 3px solid rgba(128, 128, 128, 0.25);
  border-top-color: var(--accent, #5b8def);
  border-radius: 50%;
  animation: viewer-spin 0.9s linear infinite;
}
@keyframes viewer-spin { to { transform: rotate(360deg); } }

.viewer-stage {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #000;
  cursor: pointer;
}
/* 沉浸背景层：幻灯片背景色的同色柔和延伸，不做明暗/饱和处理，过渡平缓 */
.viewer-backdrop {
  position: absolute;
  inset: -40px;
  filter: blur(48px);
}
.viewer-backdrop + .slide-layer { box-shadow: 0 12px 40px rgba(0, 0, 0, .25); }
.viewer-hud {
  position: fixed;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 12px;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  font-size: 12px;
  user-select: none;
  pointer-events: none;
  transition: opacity 0.5s;
}
.viewer-stage.idle .viewer-hud { opacity: 0; }
</style>
