export type DemoGamePreset = 'classic' | 'score-o'
export type BusinessLaunchSource = 'demo' | 'competition' | 'direct-event' | 'custom'

export interface GameConfigLaunchRequest {
  configUrl: string
  configLabel: string
  configChecksumSha256?: string | null
  releaseId?: string | null
  routeCode?: string | null
}

export interface BusinessLaunchContext {
  source: BusinessLaunchSource
  competitionId?: string | null
  eventId?: string | null
  launchRequestId?: string | null
  participantId?: string | null
  sessionId?: string | null
  sessionToken?: string | null
  sessionTokenExpiresAt?: string | null
  realtimeEndpoint?: string | null
  realtimeToken?: string | null
}

export interface GameVariantLaunchContext {
  variantId?: string | null
  variantName?: string | null
  routeCode?: string | null
  assignmentMode?: string | null
}

export interface GameRuntimeLaunchContext {
  runtimeBindingId?: string | null
  placeId?: string | null
  placeName?: string | null
  mapId?: string | null
  mapName?: string | null
  tileReleaseId?: string | null
  courseSetId?: string | null
  courseVariantId?: string | null
  routeCode?: string | null
}

export interface GamePresentationLaunchContext {
  presentationId?: string | null
  templateKey?: string | null
  version?: string | null
}

export interface GameContentBundleLaunchContext {
  bundleId?: string | null
  bundleType?: string | null
  version?: string | null
}

export interface GameLaunchEnvelope {
  config: GameConfigLaunchRequest
  business: BusinessLaunchContext | null
  variant?: GameVariantLaunchContext | null
  runtime?: GameRuntimeLaunchContext | null
  presentation?: GamePresentationLaunchContext | null
  contentBundle?: GameContentBundleLaunchContext | null
}

export interface MapPageLaunchOptions {
  launchId?: string
  recoverSession?: string
  preset?: string
  configUrl?: string
  configLabel?: string
  configChecksumSha256?: string
  releaseId?: string
  routeCode?: string
  launchSource?: string
  competitionId?: string
  eventId?: string
  launchRequestId?: string
  participantId?: string
  sessionId?: string
  sessionToken?: string
  sessionTokenExpiresAt?: string
  realtimeEndpoint?: string
  realtimeToken?: string
  variantId?: string
  variantName?: string
  assignmentMode?: string
  runtimeBindingId?: string
  placeId?: string
  placeName?: string
  mapId?: string
  mapName?: string
  tileReleaseId?: string
  courseSetId?: string
  courseVariantId?: string
  presentationId?: string
  presentationTemplateKey?: string
  presentationVersion?: string
  contentBundleId?: string
  contentBundleType?: string
  contentBundleVersion?: string
}

type PendingGameLaunchStore = Record<string, GameLaunchEnvelope>

const PENDING_GAME_LAUNCH_STORAGE_KEY = 'cmr.pendingGameLaunch.v1'
const CLASSIC_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/classic-sequential.json'
const SCORE_O_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/score-o.json'

function normalizeOptionalString(value: unknown): string | null {
  if (typeof value !== 'string') {
    return null
  }

  const normalized = decodeURIComponent(value).trim()
  return normalized ? normalized : null
}

function resolveDemoPreset(value: string | null): DemoGamePreset {
  return value === 'score-o' ? 'score-o' : 'classic'
}

function resolveBusinessLaunchSource(value: string | null): BusinessLaunchSource {
  if (value === 'competition' || value === 'direct-event' || value === 'custom') {
    return value
  }

  return 'demo'
}

function buildDemoConfig(preset: DemoGamePreset): GameConfigLaunchRequest {
  if (preset === 'score-o') {
    return {
      configUrl: SCORE_O_REMOTE_GAME_CONFIG_URL,
      configLabel: '积分赛配置',
    }
  }

  return {
    configUrl: CLASSIC_REMOTE_GAME_CONFIG_URL,
    configLabel: '顺序赛配置',
  }
}

function hasBusinessFields(context: Omit<BusinessLaunchContext, 'source'>): boolean {
  return Object.values(context).some((value) => typeof value === 'string' && value.length > 0)
}

function buildBusinessLaunchContext(options?: MapPageLaunchOptions | null): BusinessLaunchContext | null {
  if (!options) {
    return null
  }

  const context = {
    competitionId: normalizeOptionalString(options.competitionId),
    eventId: normalizeOptionalString(options.eventId),
    launchRequestId: normalizeOptionalString(options.launchRequestId),
    participantId: normalizeOptionalString(options.participantId),
    sessionId: normalizeOptionalString(options.sessionId),
    sessionToken: normalizeOptionalString(options.sessionToken),
    sessionTokenExpiresAt: normalizeOptionalString(options.sessionTokenExpiresAt),
    realtimeEndpoint: normalizeOptionalString(options.realtimeEndpoint),
    realtimeToken: normalizeOptionalString(options.realtimeToken),
  }

  const launchSource = normalizeOptionalString(options.launchSource)
  if (!hasBusinessFields(context) && launchSource === null) {
    return null
  }

  return {
    source: resolveBusinessLaunchSource(launchSource),
    ...context,
  }
}

function buildVariantLaunchContext(options?: MapPageLaunchOptions | null): GameVariantLaunchContext | null {
  if (!options) {
    return null
  }

  const variantId = normalizeOptionalString(options.variantId)
  const variantName = normalizeOptionalString(options.variantName)
  const routeCode = normalizeOptionalString(options.routeCode)
  const assignmentMode = normalizeOptionalString(options.assignmentMode)

  if (!variantId && !variantName && !routeCode && !assignmentMode) {
    return null
  }

  return {
    variantId,
    variantName,
    routeCode,
    assignmentMode,
  }
}

function buildRuntimeLaunchContext(options?: MapPageLaunchOptions | null): GameRuntimeLaunchContext | null {
  if (!options) {
    return null
  }

  const runtimeBindingId = normalizeOptionalString(options.runtimeBindingId)
  const placeId = normalizeOptionalString(options.placeId)
  const placeName = normalizeOptionalString(options.placeName)
  const mapId = normalizeOptionalString(options.mapId)
  const mapName = normalizeOptionalString(options.mapName)
  const tileReleaseId = normalizeOptionalString(options.tileReleaseId)
  const courseSetId = normalizeOptionalString(options.courseSetId)
  const courseVariantId = normalizeOptionalString(options.courseVariantId)
  const routeCode = normalizeOptionalString(options.routeCode)

  if (!runtimeBindingId && !placeId && !placeName && !mapId && !mapName && !tileReleaseId && !courseSetId && !courseVariantId && !routeCode) {
    return null
  }

  return {
    runtimeBindingId,
    placeId,
    placeName,
    mapId,
    mapName,
    tileReleaseId,
    courseSetId,
    courseVariantId,
    routeCode,
  }
}

function buildPresentationLaunchContext(options?: MapPageLaunchOptions | null): GamePresentationLaunchContext | null {
  if (!options) {
    return null
  }

  const presentationId = normalizeOptionalString(options.presentationId)
  const templateKey = normalizeOptionalString(options.presentationTemplateKey)
  const version = normalizeOptionalString(options.presentationVersion)

  if (!presentationId && !templateKey && !version) {
    return null
  }

  return {
    presentationId,
    templateKey,
    version,
  }
}

function buildContentBundleLaunchContext(options?: MapPageLaunchOptions | null): GameContentBundleLaunchContext | null {
  if (!options) {
    return null
  }

  const bundleId = normalizeOptionalString(options.contentBundleId)
  const bundleType = normalizeOptionalString(options.contentBundleType)
  const version = normalizeOptionalString(options.contentBundleVersion)

  if (!bundleId && !bundleType && !version) {
    return null
  }

  return {
    bundleId,
    bundleType,
    version,
  }
}

function loadPendingGameLaunchStore(): PendingGameLaunchStore {
  try {
    const stored = wx.getStorageSync(PENDING_GAME_LAUNCH_STORAGE_KEY)
    if (!stored || typeof stored !== 'object') {
      return {}
    }

    return stored as PendingGameLaunchStore
  } catch {
    return {}
  }
}

function savePendingGameLaunchStore(store: PendingGameLaunchStore): void {
  try {
    wx.setStorageSync(PENDING_GAME_LAUNCH_STORAGE_KEY, store)
  } catch {}
}

export function getDemoGameLaunchEnvelope(preset: DemoGamePreset = 'classic'): GameLaunchEnvelope {
  return {
    config: buildDemoConfig(preset),
    business: {
      source: 'demo',
    },
    variant: null,
    runtime: null,
    presentation: null,
    contentBundle: null,
  }
}

export function stashPendingGameLaunchEnvelope(envelope: GameLaunchEnvelope): string {
  const launchId = `launch_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
  const store = loadPendingGameLaunchStore()
  store[launchId] = envelope
  savePendingGameLaunchStore(store)
  return launchId
}

export function consumePendingGameLaunchEnvelope(launchId: string): GameLaunchEnvelope | null {
  const normalizedLaunchId = normalizeOptionalString(launchId)
  if (!normalizedLaunchId) {
    return null
  }

  const store = loadPendingGameLaunchStore()
  const envelope = store[normalizedLaunchId] || null
  if (!envelope) {
    return null
  }

  delete store[normalizedLaunchId]
  savePendingGameLaunchStore(store)
  return envelope
}

export function buildMapPageUrlWithLaunchId(launchId: string): string {
  return `/pages/map/map?launchId=${encodeURIComponent(launchId)}`
}

export function prepareMapPageUrlForLaunch(envelope: GameLaunchEnvelope): string {
  return buildMapPageUrlWithLaunchId(stashPendingGameLaunchEnvelope(envelope))
}

export function prepareMapPageUrlForRecovery(envelope: GameLaunchEnvelope): string {
  return `${buildMapPageUrlWithLaunchId(stashPendingGameLaunchEnvelope(envelope))}&recoverSession=1`
}

export function getBackendSessionContextFromLaunchEnvelope(envelope: GameLaunchEnvelope | null | undefined): { sessionId: string; sessionToken: string } | null {
  if (!envelope || !envelope.business || !envelope.business.sessionId || !envelope.business.sessionToken) {
    return null
  }

  return {
    sessionId: envelope.business.sessionId,
    sessionToken: envelope.business.sessionToken,
  }
}

export function resolveGameLaunchEnvelope(options?: MapPageLaunchOptions | null): GameLaunchEnvelope {
  const launchId = normalizeOptionalString(options ? options.launchId : undefined)
  if (launchId) {
    const pendingEnvelope = consumePendingGameLaunchEnvelope(launchId)
    if (pendingEnvelope) {
      return pendingEnvelope
    }
  }

  const configUrl = normalizeOptionalString(options ? options.configUrl : undefined)
  if (configUrl) {
    return {
      config: {
        configUrl,
        configLabel: normalizeOptionalString(options ? options.configLabel : undefined) || '线上配置',
        configChecksumSha256: normalizeOptionalString(options ? options.configChecksumSha256 : undefined),
        releaseId: normalizeOptionalString(options ? options.releaseId : undefined),
        routeCode: normalizeOptionalString(options ? options.routeCode : undefined),
      },
      business: buildBusinessLaunchContext(options),
      variant: buildVariantLaunchContext(options),
      runtime: buildRuntimeLaunchContext(options),
      presentation: buildPresentationLaunchContext(options),
      contentBundle: buildContentBundleLaunchContext(options),
    }
  }

  const preset = resolveDemoPreset(normalizeOptionalString(options ? options.preset : undefined))
  return getDemoGameLaunchEnvelope(preset)
}
