<script lang="ts" setup>
import { usePreferredReducedMotion } from '@vueuse/core'
import { useTheme } from 'vuetify'
import { ensureRenderComplete, removeEl } from './@core/utils/dom'
import api, { type ConnectionAwareRequestConfig } from '@/api'
import { useAuthStore, useGlobalSettingsStore } from '@/stores'
import { getBrowserLocale, setI18nLanguage } from './plugins/i18n'
import { SupportedLocale } from '@/types/i18n'
import { checkAndEmitUnreadMessages } from '@/utils/badge'
import { preloadCorsImage, preloadImage } from './@core/utils/image'
import { globalLoadingStateManager } from '@/utils/loadingStateManager'
import { addBackgroundTimer, removeBackgroundTimer } from '@/utils/backgroundManager'
import PWAInstallPrompt from '@/components/pwa/PWAInstallPrompt.vue'
import SharedDialogHost from '@/components/dialog/SharedDialogHost.vue'
import { applyStoredThemeCustomizerAppearance, useEffectiveGlassSettings } from '@/composables/useThemeCustomizer'
import {
  applyStoredTransparencySettings,
  TRANSPARENCY_SETTINGS_CHANGED_EVENT,
  type TransparencyGlassQuality,
  type TransparencySettings,
} from '@/composables/useTransparencySettings'
import { completeLaunchLoading } from '@/composables/useLaunchLoading'
import { usePWA } from '@/composables/usePWA'
import { themeManager } from '@/utils/themeManager'
import { applyDocumentThemeChrome, resolveThemeName } from '@/utils/themePalette'
import { getDisplayImageUrl } from '@/utils/imageUtils'
import { configureApexChartsTheme } from '@/utils/apexCharts'
import { useGlobalOfflineStatus, type ConnectionFailureReason } from '@/composables/useOfflineStatus'
import { useAppActivityLifecycle } from '@/composables/useAppActivityLifecycle'
import {
  BACKGROUND_ROTATION_GRACE_MS,
  createBackgroundCandidateOrderResolver,
  findFirstAvailableBackground,
  preloadBackgroundRotationImages,
  shouldAllowBackgroundRotation,
} from '@/utils/backgroundRotation'
import {
  activateLoginBackgroundLayer,
  createLoginBackgroundLayers,
  getLoginGlassOpticalSettings,
  getLoginVisualProfile,
  prepareLoginBackgroundLayer,
  settleLoginBackgroundLayers,
  type LoginBackgroundLayer,
} from '@/utils/loginPresentation'
import {
  DEFAULT_GLASS_WALLPAPER_TONE_PROFILE,
  loadGlassWallpaperToneProfile,
  type GlassWallpaperToneProfile,
} from '@/utils/glassWallpaperTone'

const LOGIN_WALLPAPER_ROUTE = '/login'
const BACKGROUND_CROSSFADE_DURATION_MS = 1500
const LAUNCH_MIN_VISIBLE_MS = 320
const LAUNCH_MAX_WAIT_MS = 1200
const LAUNCH_EXIT_DURATION_MS = 180

function getLaunchNow() {
  return globalThis.performance?.now?.() ?? Date.now()
}

const launchStartedAt = Number.parseFloat(document.documentElement.dataset.launchStartedAt || '') || getLaunchNow()

function getRemainingLaunchBudget() {
  return Math.max(0, LAUNCH_MAX_WAIT_MS - (getLaunchNow() - launchStartedAt))
}

async function waitForLaunchTask(task: Promise<unknown>, timeoutMs: number, label: string) {
  if (timeoutMs <= 0) return

  await Promise.race([
    task.catch(error => {
      console.warn(`[Launch] ${label} failed`, error)
    }),
    new Promise<void>(resolve => window.setTimeout(resolve, timeoutMs)),
  ])
}

async function waitForMinimumLaunchVisibility() {
  const remaining = LAUNCH_MIN_VISIBLE_MS - (getLaunchNow() - launchStartedAt)
  if (remaining > 0) {
    await new Promise<void>(resolve => window.setTimeout(resolve, remaining))
  }
}

function getCachedAutoResolvedTheme() {
  const cachedTheme = localStorage.getItem('materio-initial-resolved-theme')

  return cachedTheme === 'dark' || cachedTheme === 'light' ? cachedTheme : null
}

function resolveInitialThemeName(themePreference: string) {
  if (themePreference === 'auto') {
    return getCachedAutoResolvedTheme() || resolveThemeName(themePreference)
  }

  return resolveThemeName(themePreference)
}

// 生效主题
const vuetifyTheme = useTheme()
const { global: globalTheme } = vuetifyTheme
let themeValue = localStorage.getItem('theme') || 'auto'
let resumeThemeSyncTimer: number | null = null
globalTheme.name.value = resolveInitialThemeName(themeValue)
applyStoredThemeCustomizerAppearance(vuetifyTheme)

// 启动屏和 iOS safe area 在同一层显示，根节点底色需要尽早和当前主题保持一致。
function syncRootLaunchPalette() {
  const { background, primary } = globalTheme.current.value.colors

  applyDocumentThemeChrome(themeValue, {
    background,
    persistLoaderColors: true,
    primary,
    resolvedTheme: globalTheme.name.value,
  })
}

// 生效语言
const localeValue = getBrowserLocale()
setI18nLanguage(localeValue as SupportedLocale)

// 检查是否登录
const authStore = useAuthStore()
const isLogin = computed(() => authStore.token)
const route = useRoute()
const router = useRouter()
const { initializePWA } = usePWA()
const offlineStatus = useGlobalOfflineStatus()

// 全局设置store
const globalSettingsStore = useGlobalSettingsStore()

// 背景图片
const backgroundImages = ref<string[]>([])
const backgroundLayers = ref(createLoginBackgroundLayers())
const backgroundToneProfiles = ref<Record<string, GlassWallpaperToneProfile>>({})
const activeImageIndex = ref(0)
const previousImageIndex = ref<number | null>(null)
const isBackgroundCrossfading = ref(false)
const backgroundCrossfadeStartedAt = ref(0)
const pendingOpticalBackgroundImage = ref('')
const resolveBackgroundCandidateOrder = createBackgroundCandidateOrderResolver()
const { allowsDecorativeMotion, isSuspended: isRenderThrottled, state: appActivityState } = useAppActivityLifecycle()
const preferredMotion = usePreferredReducedMotion()
const backgroundRotationGraceActive = ref(false)
let backgroundRotationGraceTimer: number | null = null
// 壁纸时钟允许短时后台续跑；指针、滚动和流场仍服从更严格的应用活动状态。
const allowsBackgroundRotation = computed(() =>
  shouldAllowBackgroundRotation(
    appActivityState.value,
    backgroundRotationGraceActive.value,
    preferredMotion.value === 'reduce',
  ),
)
const isTransparentTheme = computed(() => globalTheme.name.value === 'transparent')
const isGlassTheme = computed(() => globalTheme.name.value === 'glass')
const effectiveGlassSettings = useEffectiveGlassSettings()
const isInitialRouteReady = ref(false)
const isBackdropTheme = computed(() => isTransparentTheme.value || isGlassTheme.value)
const isLoginWallpaperRoute = computed(() => !isLogin.value && route.path === LOGIN_WALLPAPER_ROUTE)
const loginVisualProfile = computed(() => getLoginVisualProfile(globalTheme.name.value))
const loginGlassSettings = computed(() =>
  getLoginGlassOpticalSettings({
    appearance: effectiveGlassSettings.value.glassAppearance,
    deformationStrength: effectiveGlassSettings.value.glassDeformationStrength,
    flowStrength: effectiveGlassSettings.value.glassFlowStrength,
    preset: effectiveGlassSettings.value.glassPreset,
    reflectionStrength: effectiveGlassSettings.value.glassReflectionStrength,
    transmissionStrength: effectiveGlassSettings.value.glassTransmissionStrength,
    translationStrength: effectiveGlassSettings.value.glassTranslationStrength,
    transparencyStrength: effectiveGlassSettings.value.glassTransparencyStrength,
  }),
)
const opticalDeformationStrength = computed(() =>
  isLoginWallpaperRoute.value
    ? loginGlassSettings.value.deformationStrength
    : effectiveGlassSettings.value.glassDeformationStrength,
)
const opticalFlowStrength = computed(() =>
  isLoginWallpaperRoute.value ? loginGlassSettings.value.flowStrength : effectiveGlassSettings.value.glassFlowStrength,
)
const opticalQuality = computed(() =>
  isLoginWallpaperRoute.value ? loginGlassSettings.value.quality : effectiveGlassSettings.value.glassQuality,
)
const opticalReflectionStrength = computed(() =>
  isLoginWallpaperRoute.value
    ? loginGlassSettings.value.reflectionStrength
    : effectiveGlassSettings.value.glassReflectionStrength,
)
const opticalTransparencyStrength = computed(() =>
  isLoginWallpaperRoute.value
    ? loginGlassSettings.value.transparencyStrength
    : effectiveGlassSettings.value.glassTransparencyStrength,
)
const opticalTranslationStrength = computed(() =>
  isLoginWallpaperRoute.value
    ? loginGlassSettings.value.translationStrength
    : effectiveGlassSettings.value.glassTranslationStrength,
)
const opticalTransmissionStrength = computed(() =>
  isLoginWallpaperRoute.value
    ? loginGlassSettings.value.transmissionStrength
    : effectiveGlassSettings.value.glassTransmissionStrength,
)
const shouldUseTransparentBackgroundTreatment = computed(() => isTransparentTheme.value && Boolean(isLogin.value))
const shouldUseGlassBackgroundTreatment = computed(
  () => isGlassTheme.value && (Boolean(isLogin.value) || isLoginWallpaperRoute.value),
)
const shouldLoadBackgroundImages = computed(
  () => isLoginWallpaperRoute.value || (Boolean(isLogin.value) && isBackdropTheme.value),
)
const activeBackgroundImage = computed(() => backgroundImages.value[activeImageIndex.value] ?? '')
const renderedBackgroundLayers = computed(() => backgroundLayers.value)
const getOpticalBackgroundImage = (imageUrl: string) => getDisplayImageUrl(imageUrl, Boolean(isLogin.value))
const activeOpticalBackgroundImage = computed(() => getOpticalBackgroundImage(activeBackgroundImage.value))
const previousOpticalBackgroundImage = computed(() => {
  const previousIndex = previousImageIndex.value
  if (previousIndex === null) return ''

  return getOpticalBackgroundImage(backgroundImages.value[previousIndex] ?? '')
})
const shouldRenderGlassOpticalLayer = computed(
  () =>
    isGlassTheme.value &&
    opticalQuality.value !== 'css' &&
    isInitialRouteReady.value &&
    Boolean(activeBackgroundImage.value),
)
const GlassOpticalLayer = defineAsyncComponent(() => import('@/components/theme/GlassOpticalLayer.vue'))
const transparentBackgroundBlur = ref(16)
const transparencyGlassQuality = ref<TransparencyGlassQuality>(
  localStorage.getItem('transparency-glass-quality') === 'realtime' ? 'realtime' : 'lightweight',
)
const shouldRenderGlobalBlurLayer = computed(
  () =>
    shouldUseTransparentBackgroundTreatment.value &&
    transparentBackgroundBlur.value > 0 &&
    transparencyGlassQuality.value === 'realtime',
)
let backgroundRetryTimer: number | null = null
let backgroundRequestController: AbortController | null = null
let backgroundCrossfadeTimer: number | null = null
let pendingOpticalWallpaperTimer: number | null = null
let pendingOpticalWallpaperResolve: ((ready: boolean) => void) | null = null
let authenticatedStateTimer: number | null = null
let backgroundLoadVersion = 0
let backgroundRecoveryAttemptedVersion = -1
let backgroundRotationVersion = 0

// 读取并同步透明主题背景设置到根组件响应式状态。
function applyTransparentBackgroundSettings() {
  const settings = applyStoredTransparencySettings()

  transparentBackgroundBlur.value = settings.backgroundBlur
  transparencyGlassQuality.value = settings.glassQuality
}

// 响应透明主题设置变更事件，刷新背景模糊和玻璃质量。
function handleTransparencySettingsChanged(event: Event) {
  const { backgroundBlur, glassQuality } = (event as CustomEvent<TransparencySettings>).detail

  transparentBackgroundBlur.value = backgroundBlur
  transparencyGlassQuality.value = glassQuality
}

/** 在壁纸可见前准备稳健曝光；不可读跨域图片回落中性 profile，不阻断 CSS 背景。 */
async function ensureBackgroundToneProfile(imageUrl: string) {
  if (!imageUrl || !isGlassTheme.value) return DEFAULT_GLASS_WALLPAPER_TONE_PROFILE

  const profile = await loadGlassWallpaperToneProfile(getOpticalBackgroundImage(imageUrl))
  backgroundToneProfiles.value = {
    ...backgroundToneProfiles.value,
    [imageUrl]: profile,
  }

  return profile
}

/** 让稳定双槽位分别携带当前壁纸的曝光，交叉淡化期间不共享新图参数。 */
function getBackgroundLayerStyle(layer: LoginBackgroundLayer) {
  const profile = backgroundToneProfiles.value[layer.url] ?? DEFAULT_GLASS_WALLPAPER_TONE_PROFILE
  const appearance = effectiveGlassSettings.value.glassAppearance
  const materialExposure = appearance === 'frosted' ? 0.82 : appearance === 'tinted' ? 0.85 : 0.86

  return {
    'backgroundImage': layer.url ? `url(${layer.url})` : undefined,
    '--glass-wallpaper-brightness': String(materialExposure * profile.exposure),
  }
}

applyTransparentBackgroundSettings()

void router.isReady().then(() => {
  isInitialRouteReady.value = true
})

let heartbeatInterval: number | null = null
let connectionRetryTimer: number | null = null
let connectionProbePromise: Promise<boolean> | null = null
let connectionProbeFailures = 0
let prefersColorSchemeMediaQuery: MediaQueryList | null = null

const SERVER_PROBE_TIMEOUT_MS = 8_000
const SERVER_PROBE_FAILURE_THRESHOLD = 2
const SERVER_RETRY_DELAYS_MS = [2_000, 5_000, 10_000, 30_000] as const

/** 清除等待中的服务重连任务。 */
function clearConnectionRetryTimer() {
  if (!connectionRetryTimer) return

  window.clearTimeout(connectionRetryTimer)
  connectionRetryTimer = null
}

/** 根据浏览器状态和请求错误判断本次探测失败原因。 */
function resolveProbeFailureReason(error: unknown): ConnectionFailureReason {
  if (!offlineStatus.browserOnline.value) return 'browser-offline'

  const errorCode = (error as { code?: string } | null)?.code
  if (errorCode === 'ECONNABORTED' || errorCode === 'ETIMEDOUT') return 'timeout'

  return 'server-unreachable'
}

/** 按退避间隔安排下一次 MoviePilot 服务探测。 */
function scheduleConnectionRetry() {
  clearConnectionRetryTimer()

  const retryIndex = Math.min(Math.max(connectionProbeFailures - 1, 0), SERVER_RETRY_DELAYS_MS.length - 1)
  connectionRetryTimer = window.setTimeout(() => {
    connectionRetryTimer = null
    void probeServerConnection()
  }, SERVER_RETRY_DELAYS_MS[retryIndex])
}

/** 使用后端 ping 接口执行去重后的权威服务连通性探测。 */
async function probeServerConnection(showChecking = false): Promise<boolean> {
  if (!isLogin.value) return false
  if (connectionProbePromise) return connectionProbePromise

  clearConnectionRetryTimer()
  if (showChecking) offlineStatus.markConnectionChecking(offlineStatus.connectionReason.value ?? undefined)

  const successSequenceAtProbeStart = offlineStatus.serverSuccessSequence.value
  const probePromise = (async () => {
    try {
      await api.get('system/ping', {
        skipConnectionTracking: true,
        timeout: SERVER_PROBE_TIMEOUT_MS,
      } as ConnectionAwareRequestConfig)
      connectionProbeFailures = 0
      return true
    } catch (error) {
      if (!isLogin.value) {
        offlineStatus.markServerOnline()
        return false
      }

      // 探测期间若已有其他接口成功，则以更新的成功响应为准，避免旧失败覆盖新状态。
      if (offlineStatus.serverSuccessSequence.value > successSequenceAtProbeStart) {
        connectionProbeFailures = 0
        return true
      }

      connectionProbeFailures += 1
      const failureReason = resolveProbeFailureReason(error)

      if (connectionProbeFailures >= SERVER_PROBE_FAILURE_THRESHOLD) {
        offlineStatus.markServerOffline(failureReason)
      } else {
        offlineStatus.markConnectionChecking(failureReason)
      }

      scheduleConnectionRetry()
      return false
    }
  })()

  connectionProbePromise = probePromise
  try {
    return await probePromise
  } finally {
    if (connectionProbePromise === probePromise) connectionProbePromise = null
  }
}

/** 启动即时服务探测和五分钟在线心跳。 */
function startHeartbeat() {
  if (heartbeatInterval) window.clearInterval(heartbeatInterval)

  void probeServerConnection()

  heartbeatInterval = window.setInterval(
    async () => {
      if (isLogin.value) await probeServerConnection()
    },
    5 * 60 * 1000,
  )
}

/** 停止心跳和等待中的自动重连任务。 */
function stopHeartbeat() {
  if (heartbeatInterval) {
    window.clearInterval(heartbeatInterval)
    heartbeatInterval = null
  }

  clearConnectionRetryTimer()
  connectionProbeFailures = 0
}

watch(
  () => offlineStatus.connectionCheckRequestId.value,
  () => {
    if (isLogin.value) void probeServerConnection(true)
  },
)

watch(
  () => offlineStatus.connectionStatus.value,
  status => {
    if (status !== 'online') return
    connectionProbeFailures = 0
    clearConnectionRetryTimer()
  },
)

watch(
  () => offlineStatus.browserOnline.value,
  browserIsOnline => {
    if (!isLogin.value) return
    offlineStatus.requestConnectionCheck(browserIsOnline ? undefined : 'browser-offline')
  },
)

// 更新data-theme属性以便CSS选择器能正确匹配
function updateHtmlThemeAttribute(themeName: string) {
  document.documentElement.setAttribute('data-theme', themeName)
  document.body.setAttribute('data-theme', themeName)
  syncRootLaunchPalette()
}

// 从本地存储重新同步主题偏好、DOM 主题属性和相关外观配置。
function syncThemePreferenceFromStorage(preferCachedAuto = false) {
  if (resumeThemeSyncTimer !== null) {
    window.clearTimeout(resumeThemeSyncTimer)
    resumeThemeSyncTimer = null
  }

  themeValue = localStorage.getItem('theme') || 'auto'

  const resolvedTheme =
    themeValue === 'auto' && preferCachedAuto
      ? getCachedAutoResolvedTheme() || resolveThemeName(themeValue)
      : resolveThemeName(themeValue)
  if (globalTheme.name.value !== resolvedTheme) {
    globalTheme.name.value = resolvedTheme
  }

  applyStoredThemeCustomizerAppearance(vuetifyTheme)
  updateHtmlThemeAttribute(resolvedTheme)
  configureApexChartsTheme(resolvedTheme)

  // 前台恢复时重新跑一次主题管理器，补齐 transparent CSS 和 auto 的实际 DOM 主题。
  void themeManager
    .setTheme(themeValue === 'auto' ? resolvedTheme : themeValue)
    .then(() => {
      updateHtmlThemeAttribute(globalTheme.name.value)
    })
    .catch(error => {
      console.error('同步主题管理器失败:', error)
    })

  if (preferCachedAuto && themeValue === 'auto') {
    resumeThemeSyncTimer = window.setTimeout(() => {
      resumeThemeSyncTimer = null
      syncThemePreferenceFromStorage()
    }, 180)
  }
}

// 系统配色变化时，在自动主题模式下刷新当前实际主题。
function handleSystemThemeChange() {
  if ((localStorage.getItem('theme') || 'auto') === 'auto') {
    syncThemePreferenceFromStorage()
  }
}

/** 页面重新可见时同步主题，并在连接异常时立即重新探测服务。 */
function handleVisibilityThemeSync() {
  if (document.visibilityState === 'visible') {
    syncThemePreferenceFromStorage(true)
    if (isLogin.value && !offlineStatus.isOnline.value) offlineStatus.requestConnectionCheck()
  }
}

/** 页面从缓存或重新聚焦恢复时刷新主题偏好和异常连接状态。 */
function handlePageShowThemeSync() {
  if (document.visibilityState === 'visible') {
    if (isLogin.value && !offlineStatus.isOnline.value) offlineStatus.requestConnectionCheck()
  }
  syncThemePreferenceFromStorage(true)
}

// 清理背景图交叉淡入淡出定时器。
function clearBackgroundCrossfadeTimer() {
  if (backgroundCrossfadeTimer) {
    window.clearTimeout(backgroundCrossfadeTimer)
    backgroundCrossfadeTimer = null
  }
}

/** 结束当前 GPU 纹理预备等待，旧请求不得继续提交壁纸切换。 */
function settlePendingOpticalWallpaper(ready: boolean) {
  if (pendingOpticalWallpaperTimer !== null) {
    window.clearTimeout(pendingOpticalWallpaperTimer)
    pendingOpticalWallpaperTimer = null
  }
  pendingOpticalBackgroundImage.value = ''
  pendingOpticalWallpaperResolve?.(ready)
  pendingOpticalWallpaperResolve = null
}

/** 等待两个 WebGL 呈现 context 完成下一张纹理上传。 */
function prepareOpticalWallpaper(url: string) {
  if (!shouldRenderGlassOpticalLayer.value || !url || url === activeOpticalBackgroundImage.value) {
    return Promise.resolve(true)
  }

  settlePendingOpticalWallpaper(false)
  pendingOpticalBackgroundImage.value = url

  return new Promise<boolean>(resolve => {
    pendingOpticalWallpaperResolve = resolve
    pendingOpticalWallpaperTimer = window.setTimeout(() => settlePendingOpticalWallpaper(false), 10000)
  })
}

/** 只接受当前待切换 URL 的 renderer 就绪回执。 */
function handleOpticalWallpaperPrepared(url: string) {
  if (url === pendingOpticalBackgroundImage.value) settlePendingOpticalWallpaper(true)
}

// 重置背景图交叉淡入淡出状态。
function resetBackgroundCrossfade() {
  clearBackgroundCrossfadeTimer()
  previousImageIndex.value = null
  isBackgroundCrossfading.value = false
  backgroundCrossfadeStartedAt.value = 0
  backgroundLayers.value = createLoginBackgroundLayers(activeBackgroundImage.value)
}

// 切换期保留上一张背景的渲染状态，避免图片合成层重建时露出透明底。
function activateBackgroundImage(nextIndex: number) {
  if (nextIndex === activeImageIndex.value) return

  clearBackgroundCrossfadeTimer()
  backgroundLayers.value = prepareLoginBackgroundLayer(backgroundLayers.value, backgroundImages.value[nextIndex] ?? '')
  previousImageIndex.value = activeImageIndex.value
  isBackgroundCrossfading.value = true
  backgroundCrossfadeStartedAt.value = performance.now()
  activeImageIndex.value = nextIndex
  backgroundLayers.value = activateLoginBackgroundLayer(backgroundLayers.value)
  backgroundCrossfadeTimer = window.setTimeout(() => {
    previousImageIndex.value = null
    isBackgroundCrossfading.value = false
    backgroundLayers.value = settleLoginBackgroundLayers(backgroundLayers.value)
    backgroundCrossfadeTimer = null
  }, BACKGROUND_CROSSFADE_DURATION_MS)
}

// 获取背景图片列表；只有选出实际可用的首图后才提交到可见状态。
async function fetchBackgroundImages() {
  backgroundRequestController?.abort()
  const controller = new AbortController()
  backgroundRequestController = controller
  try {
    return await api.get<string[], string[]>(`/login/wallpapers`, {
      signal: controller.signal,
    })
  } finally {
    if (backgroundRequestController === controller) backgroundRequestController = null
  }
}

/** 仅提前加载当前图的下一项，不建立全目录预载队列。 */
function preloadNextBackgroundImage() {
  if (!allowsBackgroundRotation.value || backgroundImages.value.length <= 1) return
  const nextIndex = (activeImageIndex.value + 1) % backgroundImages.value.length
  void preloadBackgroundCandidate(backgroundImages.value[nextIndex])
}

/** 实时玻璃先建立可供 WebGL 读取的缓存，失败时仍允许 CSS 材质显示该壁纸。 */
async function preloadBackgroundCandidate(imageUrl: string) {
  const toneProfile = isGlassTheme.value
    ? ensureBackgroundToneProfile(imageUrl)
    : Promise.resolve(DEFAULT_GLASS_WALLPAPER_TONE_PROFILE)
  if (!shouldRenderGlassOpticalLayer.value) {
    const [available] = await Promise.all([preloadImage(imageUrl), toneProfile])

    return available
  }

  const opticalUrl = getOpticalBackgroundImage(imageUrl)
  const opticalReady = await preloadCorsImage(opticalUrl)
  if (!opticalReady) {
    const [displayReady] = await Promise.all([preloadImage(imageUrl), toneProfile])

    return displayReady
  }

  const [displayReady] = await Promise.all([
    opticalUrl === imageUrl ? Promise.resolve(true) : preloadImage(imageUrl),
    toneProfile,
  ])

  return displayReady
}

// 背景图片轮换函数
async function rotateBackgroundImage() {
  if (!allowsBackgroundRotation.value || backgroundImages.value.length <= 1) return

  const requestVersion = ++backgroundRotationVersion
  const activeIndex = activeImageIndex.value
  for (let offset = 1; offset < backgroundImages.value.length; offset += 1) {
    if (!allowsBackgroundRotation.value || requestVersion !== backgroundRotationVersion) return

    const nextIndex = (activeIndex + offset) % backgroundImages.value.length
    const nextImage = backgroundImages.value[nextIndex]
    const opticalImage = shouldRenderGlassOpticalLayer.value ? getOpticalBackgroundImage(nextImage) : undefined
    if (opticalImage && !(await prepareOpticalWallpaper(opticalImage))) continue
    const imagesReady = await preloadBackgroundRotationImages({
      displayUrl: nextImage,
      opticalUrl: opticalImage,
      preload: preloadImage,
    })
    if (!imagesReady) continue
    await ensureBackgroundToneProfile(nextImage)
    if (!allowsBackgroundRotation.value || requestVersion !== backgroundRotationVersion) return

    activateBackgroundImage(nextIndex)
    preloadNextBackgroundImage()
    return
  }

  if (requestVersion === backgroundRotationVersion && backgroundRecoveryAttemptedVersion !== backgroundLoadVersion) {
    stopBackgroundRotation()
    const recoveryVersion = ++backgroundLoadVersion
    backgroundRecoveryAttemptedVersion = recoveryVersion
    void loadBackgroundImages(recoveryVersion)
  }
}

// 停止轮询并使已经发起的下一图准备失效，避免非活动状态收到迟到提交。
function stopBackgroundRotation() {
  backgroundRotationVersion += 1
  removeBackgroundTimer('background-rotation')
  settlePendingOpticalWallpaper(false)
}

function clearBackgroundRotationGrace() {
  if (backgroundRotationGraceTimer !== null) {
    window.clearTimeout(backgroundRotationGraceTimer)
    backgroundRotationGraceTimer = null
  }
}

function startBackgroundRotationGrace() {
  if (backgroundRotationGraceActive.value) return

  backgroundRotationGraceActive.value = true
  clearBackgroundRotationGrace()
  backgroundRotationGraceTimer = window.setTimeout(() => {
    backgroundRotationGraceTimer = null
    backgroundRotationGraceActive.value = false
  }, BACKGROUND_ROTATION_GRACE_MS)
}

// 开始背景图片轮换
function startBackgroundRotation() {
  stopBackgroundRotation()

  if (allowsBackgroundRotation.value && backgroundImages.value.length > 1) {
    preloadNextBackgroundImage()
    // 隐藏页面也允许在有界宽限期内轮换，回调自身会再次核对生命周期。
    addBackgroundTimer(
      'background-rotation',
      () => void rotateBackgroundImage(),
      10000, // 每10秒切换一次
      {
        runInBackground: true,
        skipInitialRun: true, // 不需要立即执行
      },
    )
  }
}

watch(
  appActivityState,
  (state, previousState) => {
    if (state === 'active') {
      clearBackgroundRotationGrace()
      backgroundRotationGraceActive.value = false
      return
    }

    if (state === 'idle') {
      clearBackgroundRotationGrace()
      backgroundRotationGraceActive.value = false
      return
    }

    if (previousState === 'active') startBackgroundRotationGrace()
  },
  { flush: 'sync' },
)

watch(allowsBackgroundRotation, allowsRotation => {
  resetBackgroundCrossfade()

  if (allowsRotation) {
    startBackgroundRotation()
  } else {
    stopBackgroundRotation()
  }
})

// 停止登录页、透明主题或玻璃主题背景图加载、重试和轮播。
function stopBackgroundLoading() {
  backgroundLoadVersion += 1
  backgroundRequestController?.abort()
  backgroundRequestController = null

  if (backgroundRetryTimer) {
    window.clearTimeout(backgroundRetryTimer)
    backgroundRetryTimer = null
  }

  resetBackgroundCrossfade()
  stopBackgroundRotation()
}

// 初始化登录后的全局设置和用户设置状态。
async function initializeAuthenticatedState() {
  if (!isLogin.value) return

  try {
    globalLoadingStateManager.setLoadingState('global-settings', true)
    await globalSettingsStore.initialize()
    await globalSettingsStore.loadUserSettings()
  } finally {
    globalLoadingStateManager.setLoadingState('global-settings', false)
  }
}

// 延迟初始化登录态数据，避开登录成功后的即时路由跳转窗口。
function scheduleAuthenticatedStateInitialization() {
  if (authenticatedStateTimer) {
    window.clearTimeout(authenticatedStateTimer)
  }

  // 登录后会立刻发生路由切换，稍后再拉取设置可避开导航中止请求。
  authenticatedStateTimer = window.setTimeout(() => {
    authenticatedStateTimer = null
    initializeAuthenticatedState()
  }, 150)
}

// 添加logo动画效果并延迟移除加载界面
async function animateAndRemoveLoader() {
  const loadingBg = document.querySelector('#loading-bg') as HTMLElement
  if (loadingBg) {
    // 只收掉启动内容，背景层保持实色直到节点被移除，避免底部 safe area 先透出页面内容。
    loadingBg.classList.add('loading-complete')
    await new Promise<void>(resolve => {
      window.setTimeout(() => {
        removeEl('#loading-bg')

        // 启动阶段的根节点锁定只在 loader 存在时生效，移除后恢复正常页面与弹窗布局。
        document.documentElement.removeAttribute('data-launch-loading')
        document.documentElement.style.removeProperty('overflow')
        document.body.style.removeProperty('overflow')
        completeLaunchLoading()
        resolve()
      }, LAUNCH_EXIT_DURATION_MS)
    })
  } else {
    completeLaunchLoading()
  }
}

// 检查PWA状态并移除加载界面
async function removeLoadingWithStateCheck() {
  try {
    // 设置各个组件的加载状态
    globalLoadingStateManager.setLoadingState('pwa-state', true)

    // 静默检查PWA状态恢复，但不能让恢复异常或慢请求挡住应用外壳。
    const pwaController = (window as any).pwaStateController
    if (pwaController?.waitForStateRestore) {
      await waitForLaunchTask(
        Promise.resolve().then(() => pwaController.waitForStateRestore()),
        getRemainingLaunchBudget(),
        'PWA state restore',
      )
    }
    globalLoadingStateManager.setLoadingState('pwa-state', false)

    // PWA/App 模式会影响布局和底部导航，必须在启动屏退场前稳定下来。
    await waitForLaunchTask(initializePWA(), getRemainingLaunchBudget(), 'PWA detection')

    // 用户设置不影响首帧布局，交给应用外壳出现后继续加载。
    void initializeAuthenticatedState().catch(error => {
      console.warn('[Launch] Authenticated state initialization failed', error)
    })

    // 快速缓存命中时至少保留短暂的稳定画面，避免 iOS 只闪过一帧。
    await waitForMinimumLaunchVisibility()

    // 移除加载界面
    await animateAndRemoveLoader()

    // 检查未读消息
    if (isLogin.value) {
      checkAndEmitUnreadMessages()
    }
  } catch (error) {
    // 即使出错也要移除加载界面
    globalLoadingStateManager.reset()
    await animateAndRemoveLoader()
  }
}

// 加载背景图片
async function loadBackgroundImages(loadVersion: number, retryCount = 0) {
  const maxRetries = 3
  try {
    const images = resolveBackgroundCandidateOrder(await fetchBackgroundImages())
    if (loadVersion !== backgroundLoadVersion) return

    const firstAvailableIndex = await findFirstAvailableBackground({
      urls: images,
      canContinue: () => loadVersion === backgroundLoadVersion,
      preload: preloadBackgroundCandidate,
    })
    if (firstAvailableIndex === null) throw new Error('没有可用的登录壁纸')
    if (loadVersion !== backgroundLoadVersion) return

    const currentImage = activeBackgroundImage.value
    const currentIndex = images.indexOf(currentImage)
    if (currentImage && currentIndex < 0) {
      backgroundImages.value = [currentImage, ...images]
      activeImageIndex.value = 0
    } else {
      backgroundImages.value = images
      activeImageIndex.value = currentIndex >= 0 ? currentIndex : firstAvailableIndex
    }
    backgroundRecoveryAttemptedVersion = -1
    resetBackgroundCrossfade()
    startBackgroundRotation()
  } catch (error: any) {
    if (loadVersion !== backgroundLoadVersion) return
    const isAbortError = error.name === 'AbortError' || error.code === 'ERR_CANCELED'
    if (retryCount < maxRetries) {
      const baseDelay = isAbortError ? 1000 : 3000
      const retryDelay = Math.min(baseDelay * Math.pow(2, retryCount), 10000)
      backgroundRetryTimer = window.setTimeout(() => {
        backgroundRetryTimer = null
        if (loadVersion === backgroundLoadVersion) {
          void loadBackgroundImages(loadVersion, retryCount + 1)
        }
      }, retryDelay)
    }
  }
}

onMounted(async () => {
  // 移除URL中的时间戳参数
  const url = new URL(window.location.href)
  if (url.searchParams.has('_t')) {
    url.searchParams.delete('_t')
    const newUrl = url.pathname + url.search + url.hash
    window.history.replaceState(null, '', newUrl)
  }

  // 配置 ApexCharts
  configureApexChartsTheme(globalTheme.name.value)

  // 初始化data-theme属性
  updateHtmlThemeAttribute(globalTheme.name.value)

  // 初始化主题管理器 - 统一处理主题初始化
  await themeManager.setTheme(themeValue === 'auto' ? globalTheme.name.value : themeValue)
  applyStoredThemeCustomizerAppearance(vuetifyTheme)
  updateHtmlThemeAttribute(globalTheme.name.value)

  // 监听主题变化
  watch(
    () => globalTheme.name.value,
    newTheme => {
      // 更新HTML主题属性
      updateHtmlThemeAttribute(newTheme)
      // 重新配置ApexCharts以适应新主题
      configureApexChartsTheme(newTheme)
    },
  )

  prefersColorSchemeMediaQuery = window.matchMedia?.('(prefers-color-scheme: dark)') ?? null
  prefersColorSchemeMediaQuery?.addEventListener('change', handleSystemThemeChange)
  document.addEventListener('visibilitychange', handleVisibilityThemeSync)
  window.addEventListener('pageshow', handlePageShowThemeSync)
  window.addEventListener('focus', handlePageShowThemeSync)
  window.addEventListener(TRANSPARENCY_SETTINGS_CHANGED_EVENT, handleTransparencySettingsChanged)

  // 登录前后复用同一壁纸列表和活动项，主题变化只改变呈现方式。
  watch(
    shouldLoadBackgroundImages,
    shouldLoad => {
      stopBackgroundLoading()
      if (shouldLoad) {
        void loadBackgroundImages(backgroundLoadVersion)
      } else if (!isBackdropTheme.value) {
        backgroundImages.value = []
      }
    },
    { immediate: true },
  )

  watch(isGlassTheme, enabled => {
    if (!enabled) return

    void Promise.all(
      renderedBackgroundLayers.value.filter(layer => layer.url).map(layer => ensureBackgroundToneProfile(layer.url)),
    )
  })

  // 使用优化后的加载界面移除逻辑
  ensureRenderComplete(() => {
    nextTick(removeLoadingWithStateCheck)
  })
  // 启动心跳
  if (isLogin.value) {
    startHeartbeat()
  }

  // 登录状态可能在当前单页会话中变化，这里按需补齐登录后初始化和心跳。
  watch(isLogin, loggedIn => {
    if (loggedIn) {
      startHeartbeat()
      scheduleAuthenticatedStateInitialization()
    } else {
      if (authenticatedStateTimer) {
        window.clearTimeout(authenticatedStateTimer)
        authenticatedStateTimer = null
      }
      stopHeartbeat()
      offlineStatus.markServerOnline()
    }
  })
})

onUnmounted(() => {
  // 清除背景轮换定时器
  stopBackgroundLoading()
  clearBackgroundRotationGrace()
  if (authenticatedStateTimer) {
    window.clearTimeout(authenticatedStateTimer)
    authenticatedStateTimer = null
  }
  // 停止心跳
  stopHeartbeat()
  prefersColorSchemeMediaQuery?.removeEventListener('change', handleSystemThemeChange)
  prefersColorSchemeMediaQuery = null
  document.removeEventListener('visibilitychange', handleVisibilityThemeSync)
  window.removeEventListener('pageshow', handlePageShowThemeSync)
  window.removeEventListener('focus', handlePageShowThemeSync)
  window.removeEventListener(TRANSPARENCY_SETTINGS_CHANGED_EVENT, handleTransparencySettingsChanged)
})
</script>

<template>
  <div
    class="app-wrapper"
    :class="{
      'app-wrapper--background-transition': isBackgroundCrossfading,
      'app-wrapper--decorative-motion-paused': !allowsDecorativeMotion,
      'app-wrapper--login-glass-high': isLoginWallpaperRoute && loginVisualProfile === 'glass',
      'app-wrapper--login-wallpaper': isLoginWallpaperRoute,
      'app-wrapper--render-throttled': isRenderThrottled,
    }"
    :data-app-activity-state="appActivityState"
  >
    <!-- 登录页、透明主题和玻璃主题共用动态壁纸场景。 -->
    <div
      v-if="backgroundImages.length > 0 && (isBackdropTheme || !isLogin)"
      class="background-container"
      :class="{
        'is-transparent-theme': shouldUseTransparentBackgroundTreatment,
        'is-glass-theme': shouldUseGlassBackgroundTreatment,
        'is-transparent-glass-lightweight':
          shouldUseTransparentBackgroundTreatment && transparencyGlassQuality === 'lightweight',
      }"
    >
      <div
        v-for="layer in renderedBackgroundLayers"
        :key="layer.key"
        class="background-image"
        :class="layer.role"
        :style="getBackgroundLayerStyle(layer)"
      />
      <!-- 全局磨砂层 -->
      <div v-if="shouldRenderGlobalBlurLayer" class="global-blur-layer"></div>
    </div>
    <GlassOpticalLayer
      v-if="shouldRenderGlassOpticalLayer"
      :appearance="effectiveGlassSettings.glassAppearance"
      :deformation-strength="opticalDeformationStrength"
      :flow-strength="opticalFlowStrength"
      :quality="opticalQuality === 'high' ? 'high' : 'balanced'"
      :reflection-strength="opticalReflectionStrength"
      :transparency-strength="opticalTransparencyStrength"
      :transmission-strength="opticalTransmissionStrength"
      :translation-strength="opticalTranslationStrength"
      :route-key="route.fullPath"
      :tint-color="globalTheme.current.value.colors.primary"
      :transition-duration="BACKGROUND_CROSSFADE_DURATION_MS"
      :transition-started-at="backgroundCrossfadeStartedAt"
      :wallpaper-url="activeOpticalBackgroundImage"
      :previous-wallpaper-url="previousOpticalBackgroundImage"
      :pending-wallpaper-url="pendingOpticalBackgroundImage"
      @wallpaper-prepared="handleOpticalWallpaperPrepared"
    />
    <!-- 页面内容 -->
    <VApp
      :class="{ 'app-shell--login-wallpaper': isLoginWallpaperRoute }"
      :data-login-visual-profile="isLoginWallpaperRoute ? loginVisualProfile : undefined"
    >
      <RouterView />
      <!-- 全局共享弹窗入口，列表与卡片按需在这里挂载业务弹窗。 -->
      <SharedDialogHost />
      <!-- PWA安装提示 -->
      <PWAInstallPrompt />
    </VApp>
  </div>
</template>

<style lang="scss">
/* 全局样式 */
.app-wrapper {
  position: relative;
  inline-size: 100%;
  min-block-size: 100vh;
}

.background-container {
  position: fixed;
  z-index: 0;
  overflow: hidden;
  block-size: 100%;
  inline-size: 100%;
  inset-block-start: 0;
  inset-inline-start: 0;
}

// 登录内容与壁纸进入同一文档弹性合成上下文；sticky 仍保持普通滚动时的 viewport 锁定。
.app-wrapper--login-wallpaper .background-container {
  position: sticky;
  block-size: 100dvh;
  margin-block-end: -100dvh;
  inset-block-start: 0;
}

.background-image {
  position: absolute;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  block-size: 100%;
  inline-size: 100%;
  inset-block-start: 0;
  inset-inline-start: 0;
  opacity: 0;
  transition: opacity 1.5s ease;

  &::after {
    position: absolute;
    background: linear-gradient(rgba(0, 0, 0, 30%) 0%, rgba(0, 0, 0, 60%) 100%);
    block-size: 100%;
    content: '';
    inline-size: 100%;
    inset-block-start: 0;
    inset-inline-start: 0;
  }

  &.active {
    z-index: 2;
    opacity: 1;
  }

  &.previous {
    z-index: 1;
  }
}

.background-container.is-transparent-theme .background-image.active {
  opacity: var(--transparent-background-poster-opacity, 1);
}

.background-container.is-glass-theme .background-image.active,
.background-container.is-glass-theme .background-image.previous {
  filter: brightness(var(--glass-wallpaper-brightness, 0.86)) saturate(0.95) contrast(1.02);
}

.background-container.is-glass-theme .background-image.active {
  opacity: 0.94;
}

.background-container.is-glass-theme .background-image.active::after,
.background-container.is-glass-theme .background-image.previous::after {
  background:
    radial-gradient(circle at 50% 18%, transparent 24%, rgba(6, 10, 19, 12%) 100%),
    linear-gradient(rgba(6, 10, 19, 10%) 0%, rgba(6, 10, 19, 30%) 100%);
}

html[data-glass-appearance='tinted'] .background-container.is-glass-theme .background-image.active,
html[data-glass-appearance='tinted'] .background-container.is-glass-theme .background-image.previous {
  filter: brightness(var(--glass-wallpaper-brightness, 0.85)) saturate(0.97) contrast(1.02);
}

html[data-glass-appearance='tinted'] .background-container.is-glass-theme .background-image.active {
  opacity: 0.93;
}

html[data-glass-appearance='tinted'] .background-container.is-glass-theme .background-image.active::after,
html[data-glass-appearance='tinted'] .background-container.is-glass-theme .background-image.previous::after {
  background:
    radial-gradient(circle at 50% 18%, transparent 22%, rgba(6, 10, 19, 14%) 100%),
    linear-gradient(rgba(6, 10, 19, 10%) 0%, rgba(6, 10, 19, 32%) 100%), rgba(var(--v-theme-primary), 3%);
}

html[data-glass-appearance='frosted'] .background-container.is-glass-theme .background-image.active,
html[data-glass-appearance='frosted'] .background-container.is-glass-theme .background-image.previous {
  filter: brightness(var(--glass-wallpaper-brightness, 0.82)) saturate(0.9);
}

html[data-glass-appearance='frosted'] .background-container.is-glass-theme .background-image.active {
  opacity: 0.92;
}

html[data-glass-appearance='frosted'] .background-container.is-glass-theme .background-image.active::after,
html[data-glass-appearance='frosted'] .background-container.is-glass-theme .background-image.previous::after {
  background: linear-gradient(rgba(6, 10, 19, 24%) 0%, rgba(6, 10, 19, 48%) 100%), rgba(11, 19, 34, 8%);
}

.background-container.is-transparent-glass-lightweight .background-image.active,
.background-container.is-transparent-glass-lightweight .background-image.previous {
  filter: blur(var(--transparent-background-blur, 16px));
  transform: scale(1.03);
}

.background-container.is-transparent-glass-lightweight .background-image.active::after,
.background-container.is-transparent-glass-lightweight .background-image.previous::after {
  background: linear-gradient(rgba(0, 0, 0, 30%) 0%, rgba(0, 0, 0, 60%) 100%), rgba(128, 128, 128, 30%);
}

/* 全局磨砂层 */
.global-blur-layer {
  position: absolute;
  z-index: 3;
  backdrop-filter: blur(var(--transparent-background-blur, 16px));
  background-color: rgba(128, 128, 128, 30%);
  block-size: 100%;
  inline-size: 100%;
  inset-block-start: 0;
  inset-inline-start: 0;
}

.app-wrapper--render-throttled {
  .global-blur-layer {
    backdrop-filter: none;
  }
}

.app-wrapper--decorative-motion-paused {
  .login-ambient-light *,
  .login-logo,
  .login-logo-wrapper,
  .login-logo-wrapper::before,
  .login-title,
  .login-subtitle {
    animation-play-state: paused !important;
  }
}

/* 登录页壁纸在 VApp 外层渲染，登录页 VApp 需要透明才能露出壁纸。 */
.app-shell--login-wallpaper.v-application {
  background: transparent !important;
}
</style>
