import { loadBackendAuthTokens, loadBackendBaseUrl } from '../../utils/backendAuth'
import { getEventPlay, getPublicEventPlay, launchEvent, launchPublicEvent, type BackendCourseVariantSummary, type BackendEventPlayResult } from '../../utils/backendApi'
import { adaptBackendLaunchResultToEnvelope } from '../../utils/backendLaunchAdapter'
import { formatBackendPlayActionText, formatBackendPlayStatusText } from '../../utils/backendPlayCopy'
import { prepareMapPageUrlForLaunch } from '../../utils/gameLaunch'
import { reportBackendClientLog } from '../../utils/backendClientLogs'
import { loadRemoteMapConfig, type RemoteMapConfig } from '../../utils/remoteMapConfig'
import { buildPreparePreviewScene, buildPreparePreviewSceneFromBackendPreview, buildPreparePreviewSceneFromVariantControls, type PreparePreviewControl, type PreparePreviewScene, type PreparePreviewTile } from '../../utils/prepareMapPreview'
import { HeartRateController } from '../../engine/sensor/heartRateController'

const PREFERRED_HEART_RATE_DEVICE_STORAGE_KEY = 'cmr.preferredHeartRateDevice'
const DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY = 'cmr.debug.mockChannelId.v1'
const DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY = 'cmr.debug.autoConnectMockSources.v1'

type EventPreparePageData = {
  eventId: string
  loading: boolean
  canLaunch: boolean
  launchInFlight: boolean
  launchProgressText: string
  launchProgressPercent: number
  titleText: string
  summaryText: string
  releaseText: string
  actionText: string
  statusText: string
  assignmentMode: string
  variantModeText: string
  variantSummaryText: string
  presentationText: string
  contentBundleText: string
  runtimePlaceText: string
  runtimeMapText: string
  runtimeVariantText: string
  runtimeRouteCodeText: string
  previewVisible: boolean
  previewLoading: boolean
  previewStatusText: string
  previewHintText: string
  previewVariantText: string
  previewTiles: Array<{
    url: string
    styleText: string
  }>
  previewControls: Array<{
    label: string
    styleText: string
    kindClass: string
  }>
  selectedVariantId: string
  selectedVariantText: string
  showVariantSelector: boolean
  variantSelectorEmptyText: string
  selectableVariants: Array<{
    id: string
    name: string
    routeCodeText: string
    descriptionText: string
    selected: boolean
  }>
  locationStatusText: string
  heartRateStatusText: string
  heartRateDeviceText: string
  heartRateScanText: string
  heartRateConnected: boolean
  showHeartRateDevicePicker: boolean
  locationPermissionGranted: boolean
  locationBackgroundPermissionGranted: boolean
  heartRateDiscoveredDevices: Array<{
    deviceId: string
    name: string
    rssiText: string
    preferred: boolean
    connected: boolean
  }>
  mockSourceStatusText: string
  showMockSourceSummary: boolean
}

type EventPreparePageContext = WechatMiniprogram.Page.Instance<EventPreparePageData, Record<string, never>> & {
  previewLoadSeq?: number
  lastPlayResult?: BackendEventPlayResult | null
  previewManifestUrl?: string | null
  previewConfigCache?: RemoteMapConfig | null
  previewSceneCache?: Record<string, PreparePreviewScene>
  launchAttemptSeq?: number
  launchTimeoutTimer?: number
}

const PREVIEW_WIDTH = 640
const PREVIEW_HEIGHT = 360
const PREPARE_LAUNCH_TIMEOUT_MS = 12000

function toPercent(value: number, total: number): string {
  if (!total) {
    return '0%'
  }
  return `${(value / total) * 100}%`
}

function buildPreviewTileView(scene: PreparePreviewScene, tile: PreparePreviewTile) {
  const left = toPercent(tile.leftPx, scene.width)
  const top = toPercent(tile.topPx, scene.height)
  const width = toPercent(tile.sizePx, scene.width)
  const height = toPercent(tile.sizePx, scene.height)
  return {
    url: tile.url,
    styleText: `left:${left};top:${top};width:${width};height:${height};`,
  }
}

function buildPreviewControlView(scene: PreparePreviewScene, control: PreparePreviewControl) {
  let kindClass = 'preview-control--normal'
  if (control.kind === 'start') {
    kindClass = 'preview-control--start'
  } else if (control.kind === 'finish') {
    kindClass = 'preview-control--finish'
  }

  return {
    label: control.label,
    kindClass,
    styleText: `left:${toPercent(control.x, scene.width)};top:${toPercent(control.y, scene.height)};`,
  }
}

function resolvePreviewManifestUrl(result: BackendEventPlayResult): string {
  if (result.resolvedRelease && result.resolvedRelease.manifestUrl) {
    return result.resolvedRelease.manifestUrl
  }
  if (result.release && result.release.manifestUrl) {
    return result.release.manifestUrl
  }
  return ''
}

function canUseBackendPreview(result: BackendEventPlayResult): boolean {
  return !!(
    result.preview
    && result.preview.baseTiles
    && result.preview.baseTiles.tileBaseUrl
    && result.preview.viewport
    && typeof result.preview.viewport.minLon === 'number'
    && typeof result.preview.viewport.minLat === 'number'
    && typeof result.preview.viewport.maxLon === 'number'
    && typeof result.preview.viewport.maxLat === 'number'
  )
}

function resolveSelectedPreviewVariant(result: BackendEventPlayResult, selectedVariantId: string) {
  if (!result.preview || !result.preview.variants || !result.preview.variants.length) {
    return null
  }

  const normalizedVariantId = selectedVariantId || (result.preview.selectedVariantId || '')
  const exact = result.preview.variants.find((item) => {
    const candidateId = item.variantId || item.id || ''
    return candidateId === normalizedVariantId
  })
  if (exact) {
    return exact
  }
  return result.preview.variants[0]
}

function resolvePreviewHintText(result: BackendEventPlayResult, scene: PreparePreviewScene): string {
  if (detectMultiVariantContext(result)) {
    return scene.overlayAvailable
      ? '当前先展示低级别底图与已知赛道形态；多赛道最终以进入地图后的绑定结果为准。'
      : '当前活动支持多赛道；当前先展示底图与所选赛道信息，赛道点位预览待后端补齐每条赛道的预览数据后联动。'
  }
  return scene.overlayAvailable
    ? '当前先展示低级别底图与当前已知赛道，进入地图后按正式地图继续。'
    : '当前先展示地图范围预览，进入地图后再查看正式赛道。'
}

function detectMultiVariantContext(result: BackendEventPlayResult): boolean {
  const assignmentMode = result.play.assignmentMode
  if (assignmentMode === 'manual' || assignmentMode === 'random' || assignmentMode === 'server-assigned') {
    return true
  }

  const variants = result.play.courseVariants || []
  if (variants.length > 0) {
    return true
  }

  const haystacks = [
    result.event.displayName,
    result.event.summary,
    result.release ? result.release.configLabel : '',
    result.resolvedRelease ? result.resolvedRelease.configLabel : '',
  ]

  return haystacks.some((item) => typeof item === 'string' && item.indexOf('多赛道') >= 0)
}

function formatAssignmentMode(mode?: string | null): string {
  if (mode === 'manual') {
    return '手动选择'
  }
  if (mode === 'random') {
    return '随机分配'
  }
  if (mode === 'server-assigned') {
    return '后台指定'
  }
  return '默认单赛道'
}

function formatVariantSummary(result: BackendEventPlayResult): string {
  const variants = result.play.courseVariants || []
  if (!variants.length) {
    return '当前未声明额外赛道版本，启动时按默认赛道进入。'
  }

  const preview = variants.map((item) => {
    const title = item.routeCode || item.name
    return item.selectable === false ? `${title}(固定)` : title
  }).join(' / ')
  const selectableCount = variants.filter((item) => item.selectable !== false).length

  if (result.play.assignmentMode === 'manual') {
    return `当前活动支持 ${variants.length} 条赛道。本阶段前端先展示赛道信息，最终绑定以后端 launch 返回为准：${preview}`
  }

  if (result.play.assignmentMode === 'random') {
    return `当前活动支持 ${variants.length} 条赛道，进入地图前由后端随机绑定：${preview}`
  }

  if (result.play.assignmentMode === 'server-assigned') {
    return `当前活动赛道由后台预先指定：${preview}`
  }

  if (selectableCount > 1) {
    return `当前活动支持 ${variants.length} 条赛道。后端当前未明确返回赛道模式，前端先按手动选择兼容显示：${preview}`
  }

  return preview
}

function formatPresentationSummary(result: BackendEventPlayResult): string {
  const currentPresentation = result.currentPresentation
  if (!currentPresentation) {
    return '当前发布 release 未绑定展示版本，或当前尚未发布'
  }

  return `${currentPresentation.presentationId || '--'} / ${currentPresentation.templateKey || '--'} / ${currentPresentation.version || '--'}`
}

function formatContentBundleSummary(result: BackendEventPlayResult): string {
  const currentContentBundle = result.currentContentBundle
  if (!currentContentBundle) {
    return '当前发布 release 未绑定内容包版本，或当前尚未发布'
  }

  return `${currentContentBundle.bundleId || '--'} / ${currentContentBundle.bundleType || '--'} / ${currentContentBundle.version || '--'}`
}

function resolveSelectedVariantId(
  currentVariantId: string,
  assignmentMode?: string | null,
  variants?: BackendCourseVariantSummary[] | null,
  forceVisible?: boolean,
): string {
  if (!shouldShowVariantSelector(assignmentMode, variants, forceVisible)) {
    return ''
  }

  const selectable = (variants || []).filter((item) => item.selectable !== false)
  if (!selectable.length) {
    return ''
  }

  const currentStillExists = selectable.some((item) => item.id === currentVariantId)
  if (currentVariantId && currentStillExists) {
    return currentVariantId
  }

  return selectable[0].id
}

function buildSelectableVariants(
  selectedVariantId: string,
  assignmentMode?: string | null,
  variants?: BackendCourseVariantSummary[] | null,
  forceVisible?: boolean,
) {
  if (!shouldShowVariantSelector(assignmentMode, variants, forceVisible) || !variants || !variants.length) {
    return []
  }

  return variants
    .filter((item) => item.selectable !== false)
    .map((item) => ({
      id: item.id,
      name: item.name,
      routeCodeText: item.routeCode || '默认编码',
      descriptionText: item.description || '暂无赛道说明',
      selected: item.id === selectedVariantId,
    }))
}

function shouldShowVariantSelector(
  assignmentMode?: string | null,
  variants?: BackendCourseVariantSummary[] | null,
  forceVisible?: boolean,
): boolean {
  if (forceVisible) {
    return true
  }

  const normalizedVariants = variants || []

  if (!normalizedVariants.length) {
    return false
  }

  if (assignmentMode === 'manual') {
    return true
  }

  if (assignmentMode === 'random' || assignmentMode === 'server-assigned') {
    return false
  }

  return normalizedVariants.filter((item) => item.selectable !== false).length > 1
}

let prepareHeartRateController: HeartRateController | null = null

function clearPrepareLaunchTimeout(page: EventPreparePageContext) {
  if (page.launchTimeoutTimer) {
    clearTimeout(page.launchTimeoutTimer)
    page.launchTimeoutTimer = 0
  }
}

function resetPrepareLaunchVisualState(page: EventPreparePageContext) {
  clearPrepareLaunchTimeout(page)
  page.launchAttemptSeq = 0
  page.setData({
    launchInFlight: false,
    launchProgressText: '待进入地图',
    launchProgressPercent: 0,
  })
}

function getAccessToken(): string | null {
  const app = getApp<IAppOption>()
  const tokens = app.globalData && app.globalData.backendAuthTokens
    ? app.globalData.backendAuthTokens
    : loadBackendAuthTokens()
  return tokens && tokens.accessToken ? tokens.accessToken : null
}

function loadPreferredHeartRateDeviceName(): string | null {
  try {
    const stored = wx.getStorageSync(PREFERRED_HEART_RATE_DEVICE_STORAGE_KEY)
    if (!stored || typeof stored !== 'object') {
      return null
    }
    const normalized = stored as { name?: unknown }
    return typeof normalized.name === 'string' && normalized.name.trim().length > 0
      ? normalized.name.trim()
      : '心率带'
  } catch (_error) {
    return null
  }
}

function loadStoredMockChannelId(): string {
  try {
    const stored = wx.getStorageSync(DEBUG_MOCK_CHANNEL_ID_STORAGE_KEY)
    if (typeof stored === 'string' && stored.trim().length > 0) {
      return stored.trim()
    }
  } catch (_error) {
    return 'default'
  }
  return 'default'
}

function loadMockAutoConnectEnabled(): boolean {
  try {
    return wx.getStorageSync(DEBUG_MOCK_AUTO_CONNECT_STORAGE_KEY) === true
  } catch (_error) {
    return false
  }
}

Page({
  data: {
    eventId: '',
    loading: false,
    canLaunch: false,
    launchInFlight: false,
    launchProgressText: '待进入地图',
    launchProgressPercent: 0,
    titleText: '开始前准备',
    summaryText: '未加载',
    releaseText: '--',
    actionText: '--',
    statusText: '待加载',
    assignmentMode: '',
    variantModeText: '--',
    variantSummaryText: '--',
    presentationText: '--',
    contentBundleText: '--',
    runtimePlaceText: '进入地图后确认',
    runtimeMapText: '进入地图后确认',
    runtimeVariantText: '进入地图后确认',
    runtimeRouteCodeText: '进入地图后确认',
    previewVisible: false,
    previewLoading: false,
    previewStatusText: '准备加载地图预览',
    previewHintText: '进入地图前先看地图范围与当前已知赛道。',
    previewVariantText: '预览将跟随当前赛道选择联动',
    previewTiles: [],
    previewControls: [],
    selectedVariantId: '',
    selectedVariantText: '当前无需手动指定赛道',
    showVariantSelector: false,
    variantSelectorEmptyText: '当前无需手动指定赛道',
    selectableVariants: [],
    locationStatusText: '待进入地图后校验定位权限与实时精度',
    heartRateStatusText: '局前心率带连接入口待接入，本轮先保留骨架',
    heartRateDeviceText: '--',
    heartRateScanText: '未扫描',
    heartRateConnected: false,
    showHeartRateDevicePicker: false,
    locationPermissionGranted: false,
    locationBackgroundPermissionGranted: false,
    heartRateDiscoveredDevices: [],
    mockSourceStatusText: '模拟源调试仍在地图页调试面板中使用',
    showMockSourceSummary: false,
  } as EventPreparePageData,

  onLoad(query: { eventId?: string }) {
    const eventId = query && query.eventId ? decodeURIComponent(query.eventId) : ''
    if (!eventId) {
      this.setData({
        statusText: '缺少 eventId',
      })
      return
    }
    this.setData({ eventId })
    this.ensurePrepareHeartRateController()
    this.refreshPreparationDeviceState()
    this.loadEventPlay(eventId)
  },

  onShow() {
    resetPrepareLaunchVisualState(this as unknown as EventPreparePageContext)
    this.refreshPreparationDeviceState()
  },

  onUnload() {
    resetPrepareLaunchVisualState(this as unknown as EventPreparePageContext)
    if (prepareHeartRateController) {
      prepareHeartRateController.destroy()
      prepareHeartRateController = null
    }
  },

  async loadEventPlay(eventId?: string) {
    const targetEventId = eventId || this.data.eventId
    const accessToken = getAccessToken()

    this.setData({
      loading: true,
      statusText: '正在加载局前准备信息',
    })

    try {
      const baseUrl = loadBackendBaseUrl()
      const result = accessToken
        ? await getEventPlay({
          baseUrl,
          eventId: targetEventId,
          accessToken,
        })
        : await getPublicEventPlay({
          baseUrl,
          eventId: targetEventId,
        })
      this.applyEventPlay(result)
    } catch (error) {
      const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
      this.setData({
        loading: false,
        statusText: `局前准备加载失败：${message}`,
      })
    }
  },

  applyEventPlay(result: BackendEventPlayResult) {
    ;(this as unknown as EventPreparePageContext).lastPlayResult = result
    const page = this as unknown as EventPreparePageContext
    const nextManifestUrl = resolvePreviewManifestUrl(result)
    if (page.previewManifestUrl !== nextManifestUrl) {
      page.previewManifestUrl = nextManifestUrl
      page.previewConfigCache = null
      page.previewSceneCache = {}
    }
    const multiVariantContext = detectMultiVariantContext(result)
    const selectedVariantId = resolveSelectedVariantId(
      this.data.selectedVariantId,
      result.play.assignmentMode,
      result.play.courseVariants,
      multiVariantContext,
    )
    const assignmentMode = result.play.assignmentMode ? result.play.assignmentMode : null
    const showVariantSelector = shouldShowVariantSelector(
      result.play.assignmentMode,
      result.play.courseVariants,
      multiVariantContext,
    )
    const logVariantId = assignmentMode === 'manual' && selectedVariantId ? selectedVariantId : null
    const selectableVariants = buildSelectableVariants(
      selectedVariantId,
      result.play.assignmentMode,
      result.play.courseVariants,
      multiVariantContext,
    )
    const selectedVariant = selectableVariants.find((item) => item.id === selectedVariantId) || null
    reportBackendClientLog({
      level: 'info',
      category: 'event-prepare',
      message: 'prepare play loaded',
      eventId: result.event.id || this.data.eventId || '',
      releaseId: result.resolvedRelease && result.resolvedRelease.releaseId
        ? result.resolvedRelease.releaseId
        : '',
      manifestUrl: result.resolvedRelease && result.resolvedRelease.manifestUrl
        ? result.resolvedRelease.manifestUrl
        : '',
      details: {
        guestMode: !getAccessToken(),
        pageEventId: this.data.eventId || '',
        resultEventId: result.event.id || '',
        selectedVariantId: logVariantId,
        assignmentMode,
        variantCount: result.play.courseVariants ? result.play.courseVariants.length : 0,
        selectableVariantCount: result.play.courseVariants
          ? result.play.courseVariants.filter((item) => item.selectable !== false).length
          : 0,
        showVariantSelector,
        multiVariantContext,
      },
    })
    const variantSelectorEmptyText = multiVariantContext
      ? '当前活动按多赛道处理，但后端暂未返回可选赛道，请稍后刷新或联系后台。'
      : '当前无需手动指定赛道'
    this.setData({
      loading: false,
      canLaunch: result.play.canLaunch,
      titleText: `${result.event.displayName} / 开始前准备`,
      summaryText: result.event.summary || '暂无活动简介',
      releaseText: result.resolvedRelease
        ? `${result.resolvedRelease.configLabel} / ${result.resolvedRelease.releaseId}`
        : '当前无可用 release',
      actionText: formatBackendPlayActionText(result.play.primaryAction, result.play.reason),
      statusText: formatBackendPlayStatusText(result.play.canLaunch, result.play.primaryAction, result.play.reason),
      assignmentMode: result.play.assignmentMode || '',
      variantModeText: result.play.assignmentMode
        ? formatAssignmentMode(result.play.assignmentMode)
        : (showVariantSelector ? '手动选择' : '默认单赛道'),
      variantSummaryText: formatVariantSummary(result),
      presentationText: formatPresentationSummary(result),
      contentBundleText: formatContentBundleSummary(result),
      runtimePlaceText: '进入地图后确认',
      runtimeMapText: '进入地图后确认',
      runtimeVariantText: selectedVariant
        ? selectedVariant.name
        : (result.play.courseVariants && result.play.courseVariants[0]
          ? result.play.courseVariants[0].name
          : '进入地图后确认'),
      runtimeRouteCodeText: selectedVariant
        ? selectedVariant.routeCodeText
        : (result.play.courseVariants && result.play.courseVariants[0] && result.play.courseVariants[0].routeCode
          ? result.play.courseVariants[0].routeCode || '进入地图后确认'
          : '进入地图后确认'),
      previewVisible: true,
      previewLoading: true,
      previewStatusText: '正在生成地图预览',
      previewHintText: '进入地图前先看地图范围与当前已知赛道。',
      previewVariantText: selectedVariant
        ? `当前预览赛道：${selectedVariant.name} / ${selectedVariant.routeCodeText}`
        : (multiVariantContext ? '当前预览赛道：待选择' : '当前预览赛道：默认赛道'),
      previewTiles: [],
      previewControls: [],
      selectedVariantId,
      selectedVariantText: selectedVariant
        ? `${selectedVariant.name} / ${selectedVariant.routeCodeText}`
        : variantSelectorEmptyText,
      showVariantSelector,
      variantSelectorEmptyText,
      selectableVariants,
    })
    this.loadPrepareMapPreview(result)
  },

  async loadPrepareMapPreview(result: BackendEventPlayResult) {
    const page = this as unknown as EventPreparePageContext
    const seq = (page.previewLoadSeq || 0) + 1
    page.previewLoadSeq = seq
    const selectedVariantId = this.data.selectedVariantId || (result.preview && result.preview.selectedVariantId ? result.preview.selectedVariantId : '')
    const manifestUrl = resolvePreviewManifestUrl(result)
    let fallbackConfig: RemoteMapConfig | null = page.previewConfigCache || null
    const multiVariantContext = detectMultiVariantContext(result)

    if (multiVariantContext && canUseBackendPreview(result) && result.preview) {
      const sceneCacheKey = selectedVariantId || '__default__'
      const cachedScene = page.previewSceneCache && page.previewSceneCache[sceneCacheKey]
      if (cachedScene) {
        const previewTiles = cachedScene.tiles.map((item) => buildPreviewTileView(cachedScene, item))
        const previewControls = cachedScene.controls.map((item) => buildPreviewControlView(cachedScene, item))
        this.setData({
          previewVisible: true,
          previewLoading: false,
          previewStatusText: cachedScene.overlayAvailable ? '已加载地图与赛道预览' : '已加载地图范围预览',
          previewHintText: cachedScene.overlayAvailable
            ? '当前预览已按所选赛道联动显示点位，最终绑定以后端 launch 返回结果为准。'
            : '当前预览已切换到所选赛道的底图范围；该赛道暂未返回点位预览数据。',
          previewVariantText: selectedVariantId
            ? `当前预览赛道：${this.data.selectedVariantText}`
            : '当前预览赛道：默认赛道',
          previewTiles,
          previewControls,
          runtimePlaceText: result.event.displayName || this.data.runtimePlaceText,
        })
        return
      }

      if (manifestUrl) {
        if (!fallbackConfig) {
          try {
            fallbackConfig = await loadRemoteMapConfig(manifestUrl)
            page.previewConfigCache = fallbackConfig
          } catch (_error) {
            fallbackConfig = null
          }
        }
      }

      const selectedPreviewVariant = resolveSelectedPreviewVariant(result, selectedVariantId)
      const scene = fallbackConfig && selectedPreviewVariant && selectedPreviewVariant.controls
        ? buildPreparePreviewSceneFromVariantControls(
          fallbackConfig,
          PREVIEW_WIDTH,
          PREVIEW_HEIGHT,
          selectedPreviewVariant.controls,
        )
        : buildPreparePreviewSceneFromBackendPreview(
          result.preview,
          PREVIEW_WIDTH,
          PREVIEW_HEIGHT,
          selectedVariantId,
          fallbackConfig ? fallbackConfig.tileSource : null,
        )
      if (page.previewLoadSeq !== seq) {
        return
      }

      if (scene) {
        if (!page.previewSceneCache) {
          page.previewSceneCache = {}
        }
        page.previewSceneCache[sceneCacheKey] = scene
        const previewTiles = scene.tiles.map((item) => buildPreviewTileView(scene, item))
        const previewControls = scene.controls.map((item) => buildPreviewControlView(scene, item))
        this.setData({
          previewVisible: true,
          previewLoading: false,
          previewStatusText: scene.overlayAvailable ? '已加载地图与赛道预览' : '已加载地图范围预览',
          previewHintText: scene.overlayAvailable
            ? '当前预览已按所选赛道联动显示点位，最终绑定以后端 launch 返回结果为准。'
            : '当前预览已切换到所选赛道的底图范围；该赛道暂未返回点位预览数据。',
          previewVariantText: selectedVariantId
            ? `当前预览赛道：${this.data.selectedVariantText}`
            : '当前预览赛道：默认赛道',
          previewTiles,
          previewControls,
          runtimePlaceText: result.event.displayName || this.data.runtimePlaceText,
        })
        return
      }

    }

    if (!manifestUrl) {
      this.setData({
        previewVisible: true,
        previewLoading: false,
        previewStatusText: '当前发布未返回预览底图来源',
        previewHintText: '当前活动暂无可用地图预览，请稍后刷新或联系后台。',
        previewVariantText: '当前预览赛道：待进入地图后确认',
        previewTiles: [],
        previewControls: [],
      })
      return
    }

    try {
      const config = fallbackConfig || await loadRemoteMapConfig(manifestUrl)
      page.previewConfigCache = config
      if (page.previewLoadSeq !== seq) {
        return
      }

      const overlayEnabled = !multiVariantContext
      const scene = buildPreparePreviewScene(config, PREVIEW_WIDTH, PREVIEW_HEIGHT, overlayEnabled)
      const previewTiles = scene.tiles.map((item) => buildPreviewTileView(scene, item))
      const previewControls = scene.controls.map((item) => buildPreviewControlView(scene, item))
      const runtimeMapText = config.configTitle || '进入地图后确认'
      const runtimePlaceText = result.event.displayName || '进入地图后确认'
      this.setData({
        previewVisible: true,
        previewLoading: false,
        previewStatusText: scene.overlayAvailable ? '已加载地图与赛道预览' : '已加载地图范围预览',
        previewHintText: resolvePreviewHintText(result, scene),
        previewVariantText: this.data.selectedVariantId
          ? `当前预览赛道：${this.data.selectedVariantText}`
          : (result.play.courseVariants && result.play.courseVariants[0]
            ? `当前预览赛道：${result.play.courseVariants[0].name} / ${result.play.courseVariants[0].routeCode || '默认编码'}`
            : '当前预览赛道：默认赛道'),
        previewTiles,
        previewControls,
        runtimePlaceText,
        runtimeMapText,
      })
    } catch (error) {
      if (page.previewLoadSeq !== seq) {
        return
      }
      const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
      this.setData({
        previewVisible: true,
        previewLoading: false,
        previewStatusText: `地图预览加载失败：${message}`,
        previewHintText: '当前先展示文字摘要；预览底图可在刷新后重试。',
        previewVariantText: '当前预览赛道：待进入地图后确认',
        previewTiles: [],
        previewControls: [],
      })
    }
  },

  refreshPreparationDeviceState() {
    this.refreshLocationPermissionStatus()
    this.refreshHeartRatePreparationStatus()
    this.refreshMockSourcePreparationStatus()
  },

  ensurePrepareHeartRateController() {
    if (prepareHeartRateController) {
      return prepareHeartRateController
    }

    prepareHeartRateController = new HeartRateController({
      onHeartRate: () => {},
      onStatus: (message) => {
        this.setData({
          heartRateStatusText: message,
        })
      },
      onError: (message) => {
        this.setData({
          heartRateStatusText: message,
        })
      },
      onConnectionChange: (connected, deviceName) => {
        this.setData({
          heartRateConnected: connected,
          heartRateDeviceText: connected ? (deviceName || '心率带') : (deviceName || '--'),
        })
        this.refreshHeartRatePreparationStatus()
      },
      onDeviceListChange: (devices) => {
        this.setData({
          heartRateScanText: devices.length ? `已发现 ${devices.length} 个设备` : '未扫描',
          heartRateDiscoveredDevices: devices.map((device) => ({
            deviceId: device.deviceId,
            name: device.name,
            rssiText: typeof device.rssi === 'number' ? `${device.rssi} dBm` : 'RSSI --',
            preferred: !!device.isPreferred,
            connected: !!prepareHeartRateController
              && !!prepareHeartRateController.currentDeviceId
              && prepareHeartRateController.currentDeviceId === device.deviceId
              && prepareHeartRateController.connected,
          })),
        })
      },
    })

    return prepareHeartRateController
  },

  refreshLocationPermissionStatus() {
    wx.getSetting({
      success: (result) => {
        const authSetting = result && result.authSetting
          ? result.authSetting as Record<string, boolean | undefined>
          : {}
        const hasForeground = authSetting['scope.userLocation'] === true
        const hasBackground = authSetting['scope.userLocationBackground'] === true
        let locationStatusText = '未请求定位权限'
        if (hasForeground && hasBackground) {
          locationStatusText = '已授权前后台定位'
        } else if (hasForeground) {
          locationStatusText = '已授权前台定位'
        } else if (authSetting['scope.userLocation'] === false) {
          locationStatusText = '定位权限被拒绝'
        }
        this.setData({
          locationStatusText,
          locationPermissionGranted: hasForeground,
          locationBackgroundPermissionGranted: hasBackground,
        })
      },
      fail: () => {
        this.setData({
          locationStatusText: '无法读取定位权限状态',
          locationPermissionGranted: false,
          locationBackgroundPermissionGranted: false,
        })
      },
    })
  },

  handleRequestLocationPermission() {
    wx.authorize({
      scope: 'scope.userLocation',
      success: () => {
        this.refreshLocationPermissionStatus()
        wx.showToast({
          title: '前台定位已授权',
          icon: 'none',
        })
      },
      fail: () => {
        this.refreshLocationPermissionStatus()
        wx.showToast({
          title: '请在设置中开启定位权限',
          icon: 'none',
        })
      },
    })
  },

  handleOpenLocationSettings() {
    wx.openSetting({
      success: () => {
        this.refreshLocationPermissionStatus()
      },
      fail: () => {
        wx.showToast({
          title: '无法打开设置面板',
          icon: 'none',
        })
      },
    })
  },

  refreshHeartRatePreparationStatus() {
    const controller = this.ensurePrepareHeartRateController()
    const preferredDeviceName = loadPreferredHeartRateDeviceName()
    this.setData({
      heartRateStatusText: controller.connected
        ? '局前心率带已连接'
        : preferredDeviceName
          ? `已记住首选设备：${preferredDeviceName}`
          : '未设置首选设备，可在此连接或进入地图后连接',
      heartRateDeviceText: controller.currentDeviceName || preferredDeviceName || '--',
      heartRateScanText: controller.scanning
        ? '扫描中'
        : (controller.discoveredDevices.length ? `已发现 ${controller.discoveredDevices.length} 个设备` : '未扫描'),
      heartRateConnected: controller.connected,
      heartRateDiscoveredDevices: controller.discoveredDevices.map((device) => ({
        deviceId: device.deviceId,
        name: device.name,
        rssiText: typeof device.rssi === 'number' ? `${device.rssi} dBm` : 'RSSI --',
        preferred: !!device.isPreferred,
        connected: !!controller.currentDeviceId && controller.currentDeviceId === device.deviceId && controller.connected,
      })),
    })
  },

  refreshMockSourcePreparationStatus() {
    const channelId = loadStoredMockChannelId()
    const autoConnect = loadMockAutoConnectEnabled()
    const showMockSourceSummary = autoConnect || channelId !== 'default'
    this.setData({
      mockSourceStatusText: autoConnect
        ? `调试源自动连接已开启 / 通道 ${channelId}`
        : `当前使用调试通道 ${channelId}`,
      showMockSourceSummary,
    })
  },

  handleRefresh() {
    this.loadEventPlay()
  },

  handleBack() {
    wx.navigateBack()
  },

  handlePrepareHeartRateConnect() {
    const controller = this.ensurePrepareHeartRateController()
    controller.startScanAndConnect()
    this.refreshHeartRatePreparationStatus()
  },

  handleOpenHeartRateDevicePicker() {
    const controller = this.ensurePrepareHeartRateController()
    this.setData({
      showHeartRateDevicePicker: true,
    })
    if (!controller.scanning) {
      controller.startScanAndConnect()
    }
    this.refreshHeartRatePreparationStatus()
  },

  handleCloseHeartRateDevicePicker() {
    this.setData({
      showHeartRateDevicePicker: false,
    })
  },

  handlePrepareHeartRateDeviceConnect(event: WechatMiniprogram.BaseEvent<{ deviceId?: string }>) {
    const deviceId = event.currentTarget.dataset.deviceId
    if (!deviceId) {
      return
    }
    const controller = this.ensurePrepareHeartRateController()
    controller.connectToDiscoveredDevice(deviceId)
    this.setData({
      showHeartRateDevicePicker: false,
    })
    this.refreshHeartRatePreparationStatus()
  },

  handlePrepareHeartRateDisconnect() {
    if (!prepareHeartRateController) {
      return
    }
    prepareHeartRateController.disconnect()
    this.setData({
      heartRateConnected: false,
    })
    this.refreshHeartRatePreparationStatus()
  },

  handlePrepareHeartRateClearPreferred() {
    const controller = this.ensurePrepareHeartRateController()
    controller.clearPreferredDevice()
    this.refreshHeartRatePreparationStatus()
  },

  handleSelectVariant(event: WechatMiniprogram.BaseEvent<{ variantId?: string }>) {
    const variantId = event.currentTarget.dataset.variantId
    if (!variantId) {
      return
    }

    const selectableVariants = this.data.selectableVariants.map((item) => ({
      ...item,
      selected: item.id === variantId,
    }))
    const selectedVariant = selectableVariants.find((item) => item.id === variantId) || null
    this.setData({
      selectedVariantId: variantId,
      selectedVariantText: selectedVariant
        ? `${selectedVariant.name} / ${selectedVariant.routeCodeText}`
        : '当前无需手动指定赛道',
      runtimeVariantText: selectedVariant ? selectedVariant.name : '进入地图后确认',
      runtimeRouteCodeText: selectedVariant ? selectedVariant.routeCodeText : '进入地图后确认',
      previewHintText: selectedVariant
        ? (this.data.showVariantSelector
          ? `当前已选 ${selectedVariant.name} / ${selectedVariant.routeCodeText}；预览底图会保留不变，最终赛道以 launch 绑定结果为准。`
          : `当前已选 ${selectedVariant.name} / ${selectedVariant.routeCodeText}；最终地图以 launch 绑定结果为准。`)
        : this.data.previewHintText,
      previewStatusText: this.data.showVariantSelector ? '已加载地图范围预览' : this.data.previewStatusText,
      previewVariantText: selectedVariant
        ? `当前预览赛道：${selectedVariant.name} / ${selectedVariant.routeCodeText}`
        : '当前预览赛道：待选择',
      selectableVariants,
    })

    const page = this as unknown as EventPreparePageContext
    if (page.lastPlayResult) {
      this.loadPrepareMapPreview(page.lastPlayResult)
    }
  },

  async handleLaunch() {
    const page = this as unknown as EventPreparePageContext
    const accessToken = getAccessToken()
    if (this.data.launchInFlight) {
      wx.showToast({
        title: '正在进入地图，请稍候',
        icon: 'none',
      })
      return
    }
    if (!this.data.canLaunch) {
      this.setData({
        statusText: '当前发布状态不可进入地图',
      })
      wx.showToast({
        title: '当前发布状态不可进入地图',
        icon: 'none',
      })
      return
    }

    if (!this.data.locationPermissionGranted) {
      this.setData({
        statusText: '进入地图前请先完成定位授权',
      })
      wx.showToast({
        title: '请先授权定位',
        icon: 'none',
      })
      return
    }

    this.setData({
      launchInFlight: true,
      launchProgressText: '正在校验并创建本局',
      launchProgressPercent: 24,
      statusText: '正在创建 session 并进入地图',
    })
    const launchSeq = (page.launchAttemptSeq || 0) + 1
    page.launchAttemptSeq = launchSeq
    clearPrepareLaunchTimeout(page)
    page.launchTimeoutTimer = setTimeout(() => {
      if (page.launchAttemptSeq !== launchSeq) {
        return
      }
      this.setData({
        launchInFlight: false,
        launchProgressText: '进入地图超时',
        launchProgressPercent: 0,
        statusText: '进入地图超时，请稍后重试',
      })
      wx.showToast({
        title: '进入地图超时，请重试',
        icon: 'none',
      })
    }, PREPARE_LAUNCH_TIMEOUT_MS) as unknown as number

    try {
      const assignmentMode = this.data.assignmentMode ? this.data.assignmentMode : null
      const selectedVariantId = this.data.showVariantSelector && this.data.selectedVariantId
        ? this.data.selectedVariantId
        : null
      reportBackendClientLog({
        level: 'info',
        category: 'event-prepare',
        message: 'launch requested',
        eventId: this.data.eventId || '',
        details: {
          pageEventId: this.data.eventId || '',
          selectedVariantId,
          assignmentMode,
          phase: 'launch-requested',
        },
      })
      this.setData({
        launchProgressText: '已发起启动请求，正在等待服务器响应',
        launchProgressPercent: 52,
      })
      const app = getApp<IAppOption>()
      if (app.globalData) {
        const pendingDeviceName = prepareHeartRateController && prepareHeartRateController.currentDeviceName
          ? prepareHeartRateController.currentDeviceName
          : loadPreferredHeartRateDeviceName()
        app.globalData.pendingHeartRateAutoConnect = {
          enabled: !!pendingDeviceName,
          deviceName: pendingDeviceName || null,
        }
      }
      if (prepareHeartRateController) {
        prepareHeartRateController.destroy()
        prepareHeartRateController = null
      }
      const result = accessToken
        ? await launchEvent({
          baseUrl: loadBackendBaseUrl(),
          eventId: this.data.eventId,
          accessToken,
          variantId: this.data.showVariantSelector ? this.data.selectedVariantId : undefined,
          clientType: 'wechat',
          deviceKey: 'mini-dev-device-001',
        })
        : await launchPublicEvent({
          baseUrl: loadBackendBaseUrl(),
          eventId: this.data.eventId,
          variantId: this.data.showVariantSelector ? this.data.selectedVariantId : undefined,
          clientType: 'wechat',
          deviceKey: 'mini-dev-device-001',
        })
      if (page.launchAttemptSeq !== launchSeq) {
        return
      }
      clearPrepareLaunchTimeout(page)
      this.setData({
        launchProgressText: '启动成功，正在载入地图',
        launchProgressPercent: 86,
      })
      reportBackendClientLog({
        level: 'info',
        category: 'event-prepare',
        message: 'launch response received',
        eventId: result.launch.business && result.launch.business.eventId ? result.launch.business.eventId : this.data.eventId || '',
        releaseId: result.launch.config && result.launch.config.releaseId ? result.launch.config.releaseId : '',
        sessionId: result.launch.business && result.launch.business.sessionId ? result.launch.business.sessionId : '',
        manifestUrl: result.launch.resolvedRelease && result.launch.resolvedRelease.manifestUrl
          ? result.launch.resolvedRelease.manifestUrl
          : '',
        details: {
          guestMode: !accessToken,
          pageEventId: this.data.eventId || '',
          launchEventId: result.launch.business && result.launch.business.eventId ? result.launch.business.eventId : '',
          launchSessionId: result.launch.business && result.launch.business.sessionId ? result.launch.business.sessionId : '',
          configUrl: result.launch.config && result.launch.config.configUrl ? result.launch.config.configUrl : '',
          releaseId: result.launch.config && result.launch.config.releaseId ? result.launch.config.releaseId : '',
          resolvedReleaseId: result.launch.resolvedRelease && result.launch.resolvedRelease.releaseId
            ? result.launch.resolvedRelease.releaseId
            : '',
          resolvedManifestUrl: result.launch.resolvedRelease && result.launch.resolvedRelease.manifestUrl
            ? result.launch.resolvedRelease.manifestUrl
            : '',
          launchVariantId: result.launch.variant && result.launch.variant.id ? result.launch.variant.id : null,
          phase: 'launch-response',
        },
      })
      const envelope = adaptBackendLaunchResultToEnvelope(result)
      wx.navigateTo({
        url: prepareMapPageUrlForLaunch(envelope),
      })
    } catch (error) {
      if (page.launchAttemptSeq !== launchSeq) {
        return
      }
      clearPrepareLaunchTimeout(page)
      const message = error && (error as { message?: string }).message ? (error as { message: string }).message : '未知错误'
      this.setData({
        launchInFlight: false,
        launchProgressText: '进入地图失败',
        launchProgressPercent: 0,
        statusText: `launch 失败：${message}`,
      })
    }
  },
})
