<template>
  <GuidePageFrame
    active-tab="guide"
    variant="overlay"
    show-back
    :show-cancel="isManualLocationPanelOpen"
    @tab-change="handleTopTabChange"
    @back="handlePageBack"
    @cancel="handleManualLocationCancel"
  >
    <GuideMapShell
      v-bind="shellConfig"
      :indoor-model-source="indoorModelSource"
      :floors="guideFloors"
      :normalize-floor-id="normalizeGuideFloorId"
      :target-focus-request="targetFocusRequest"
      @search-tap="handleSearchTap"
      @mode-change="handleModeChange"
      @floor-change="handleFloorChange"
      @tool-click="handleToolClick"
      @target-focus="handleTargetFocus"
    >
      <template #overlay>
        <view
          v-if="routeViewState === 'location-error' && !isManualLocationPanelOpen"
          class="location-error-dialog"
        >
          <view class="error-icon">
            <text class="error-icon-mark">!</text>
          </view>
          <text class="error-title">无法自动获取起点</text>
          <text class="error-desc">
            可以返回位置预览，或手动选择预览起点所在楼层与区域。
          </text>
          <view class="error-actions">
            <view class="dialog-btn secondary" @tap="handleRelocate">
              <text class="dialog-btn-text">返回预览</text>
            </view>
            <view class="dialog-btn primary" @tap="handleManualSelect">
              <text class="dialog-btn-text">手动选择起点</text>
            </view>
          </view>
        </view>

        <view
          v-if="routeViewState === 'location-error' && isManualLocationPanelOpen"
          class="manual-location-sheet"
        >
          <text class="manual-title">选择楼层与区域</text>
          <view class="chip-row floor-chip-row">
            <view
              v-for="floor in manualFloors"
              :key="floor"
              class="manual-chip"
              :class="{ active: floor === selectedManualFloor }"
              @tap="handleManualFloorChange(floor)"
            >
              <text class="manual-chip-text">{{ floor }}</text>
            </view>
          </view>
          <view class="chip-row area-chip-row">
            <view
              v-for="area in manualAreas"
              :key="area"
              class="manual-chip area"
              :class="{ active: area === selectedManualArea }"
              @tap="handleManualAreaChange(area)"
            >
              <text class="manual-chip-text">{{ area }}</text>
            </view>
          </view>
          <view class="manual-location-actions">
            <view class="manual-location-btn secondary" @tap="handleManualLocationCancel">
              <text class="manual-location-btn-text">取消</text>
            </view>
            <view class="manual-location-btn primary" @tap="handleConfirmLocation">
              <text class="manual-location-btn-text">
                确认预览起点：{{ selectedManualFloor }} {{ selectedManualArea }}
              </text>
            </view>
          </view>
        </view>

        <view v-if="routeViewState === 'outdoor-preview'" class="entrance-tip">
          <text class="entrance-tip-text">主入口 约 4 分钟</text>
        </view>
      </template>

      <view v-if="!hasRouteTarget" class="target-required-card">
        <text class="target-required-title">请选择目标位置</text>
        <text class="target-required-desc">需要先选择展厅或设施，才能查看馆内三维位置。</text>
        <view class="target-required-btn" @tap="handleSelectTarget">
          <text class="target-required-btn-text">选择目标地点</text>
        </view>
      </view>

      <LocationPreview
        v-else-if="routeViewState === 'preview'"
        :summary="route.summary"
        :steps="route.steps"
        :target-location="route.targetLocation"
        @view-outdoor-map="handleViewOutdoorMap"
        @show-target-location="handleShowTargetLocation"
      />

      <view v-else-if="routeViewState === 'outdoor-preview'" class="outdoor-preview-card">
        <text class="outdoor-preview-title">室外地图预览：{{ route.target }}</text>
        <text class="outdoor-preview-desc">
          室外地图仅作入口参考；馆内当前支持三维位置预览。
        </text>
        <view class="outdoor-preview-actions">
          <view class="outdoor-preview-btn secondary" @tap="handleReturnToPreview">
            <text class="outdoor-preview-btn-text">返回预览</text>
          </view>
          <view class="outdoor-preview-btn primary" @tap="handleReturnToPreview">
            <text class="outdoor-preview-btn-text">返回室内3D</text>
          </view>
        </view>
      </view>
    </GuideMapShell>
  </GuidePageFrame>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
import GuideMapShell from '@/components/navigation/GuideMapShell.vue'
import LocationPreview from '@/components/navigation/LocationPreview.vue'
import {
  guideUseCase
} from '@/usecases/guideUseCase'
import {
  initialLocationPreviewPlan,
  toTargetFocusRequestViewModel
} from '@/view-models/guideViewModels'
import type {
  GuideLocationPreview,
  GuideLocationPreviewPlan,
  GuideStartLocation,
  MuseumFloor
} from '@/domain/museum'
import {
  navigateToGuideTopTab,
  saveLastGuideLocationPreviewUrl,
  type GuideTopTab
} from '@/utils/guideTopTabs'

type RouteViewState = 'preview' | 'location-error' | 'outdoor-preview'
type GuideMode = '2d' | '3d'
type RouteTargetLocation = GuideLocationPreview
type RouteStartLocation = GuideStartLocation

interface TargetPoiFocusRequest extends RouteTargetLocation {
  requestId: number | string
}

interface TargetPoiFocusResult {
  requestId: number | string
  poiId: string
  floorId: string
  status: 'focused' | 'missing' | 'error'
  message?: string
}

type RoutePlan = GuideLocationPreviewPlan

const routeDeepLinkStates: RouteViewState[] = ['preview', 'outdoor-preview']
const indoorModelSource = guideUseCase.getModelSource()
const guideFloors = ref<MuseumFloor[]>([])
const normalizeGuideFloorId = (labelOrId: string) => guideUseCase.normalizeFloorId(labelOrId)

const route = ref<RoutePlan>(initialLocationPreviewPlan())
const hasRouteTarget = ref(false)
const routeViewState = ref<RouteViewState>('preview')
const activeFloor = ref('1F')
const targetFocusRequest = ref<TargetPoiFocusRequest | null>(null)
const targetFocusRequestId = ref(0)
const manualFloors = computed(() => guideFloors.value.map((floor) => floor.label))
const guideFloorLabel = (floorId: string) => (
  guideFloors.value.find((floor) => floor.id === floorId)?.label || floorId
)
const defaultGuideFloorLabel = () => (
  guideFloors.value.find((floor) => floor.id === 'L1')?.label
  || guideFloors.value[0]?.label
  || '1F'
)
const loadGuideFloors = async () => {
  if (guideFloors.value.length) return guideFloors.value

  try {
    guideFloors.value = await guideUseCase.getFloors()
  } catch (error) {
    console.error('加载导览楼层失败:', error)
    guideFloors.value = []
  }

  return guideFloors.value
}
const manualAreas = ['主入口', '服务台附近', '停车场入口']
const isManualLocationPanelOpen = ref(false)
const selectedManualFloor = ref('1F')
const selectedManualArea = ref('服务台附近')
const normalizeRouteOption = (value: unknown) => {
  if (Array.isArray(value)) {
    return value[0]
  }

  return value
}

const isRouteViewState = (state: unknown): state is RouteViewState => {
  const normalizedState = normalizeRouteOption(state)
  return typeof normalizedState === 'string' && routeDeepLinkStates.includes(normalizedState as RouteViewState)
}

const shellConfig = computed(() => {
  if (!hasRouteTarget.value) {
    return {
      searchText: '选择目标位置',
      searchTop: '60px',
      activeMode: '3d' as GuideMode,
      activeFloor: activeFloor.value,
      floorTop: '198px',
      toolsTop: '452px',
      tools: [],
      showSearch: true,
      showFloor: false,
      modeTop: '104px',
      modeLayout: 'full' as const,
      mapType: 'indoor' as const
    }
  }

  if (routeViewState.value === 'location-error') {
    return {
      searchText: '搜索设施、展厅、入口',
      searchTop: '60px',
      activeMode: '3d' as GuideMode,
      activeFloor: activeFloor.value,
      floorTop: '198px',
      toolsTop: '452px',
      tools: [],
      showSearch: true,
      showFloor: false,
      modeTop: '104px',
      modeLayout: 'full' as const,
      mapType: 'indoor' as const,
      dimmed: true
    }
  }

  if (routeViewState.value === 'outdoor-preview') {
    return {
      searchText: `室外地图参考：${route.value.target}`,
      searchTop: '60px',
      activeMode: '2d' as GuideMode,
      activeFloor: activeFloor.value,
      floorTop: '198px',
      toolsTop: '452px',
      tools: [],
      showSearch: true,
      showFloor: false,
      modeTop: '104px',
      modeLayout: 'status' as const,
      modeStatus: '室外参考',
      mapType: 'outdoor' as const,
      outdoorVariant: 'entrance' as const
    }
  }

  return {
    searchText: `位置预览：${route.value.target}`,
    searchTop: '60px',
    activeMode: '3d' as GuideMode,
    activeFloor: activeFloor.value,
    floorTop: '198px',
    toolsTop: '452px',
    tools: ['回正'],
    showSearch: true,
    showFloor: true,
    modeTop: '104px',
    modeLayout: 'full' as const,
    mapType: 'indoor' as const
  }
})

const safeDecode = (value: string) => {
  try {
    return decodeURIComponent(value)
  } catch (error) {
    return value
  }
}

const normalizeStringOption = (value: unknown) => {
  const normalizedValue = normalizeRouteOption(value)
  return typeof normalizedValue === 'string' ? safeDecode(normalizedValue).trim() : ''
}

const setLocationPreviewTitle = () => {
  if (typeof uni !== 'undefined') {
    uni.setNavigationBarTitle({
      title: '位置预览'
    })
  }

  if (typeof document !== 'undefined') {
    document.title = '位置预览'
  }
}

const saveCurrentLocationPreviewUrl = () => {
  if (!route.value.facilityId) return

  const params = new URLSearchParams({
    facilityId: route.value.facilityId,
    target: route.value.target,
    state: routeViewState.value
  })

  if (route.value.startLocation?.label) {
    params.set('startLabel', route.value.startLocation.label)
  }
  if (route.value.startLocation?.floor) {
    params.set('startFloor', route.value.startLocation.floor)
  }
  if (route.value.startLocation?.source) {
    params.set('startSource', route.value.startLocation.source)
  }

  saveLastGuideLocationPreviewUrl(`/pages/route/detail?${params.toString()}`)
}

const createStartLocationFromOptions = (options: Record<string, unknown>) => {
  const label = normalizeStringOption(options.startLabel)

  if (!label) return null

  const floor = normalizeStringOption(options.startFloor)
  const source = normalizeStringOption(options.startSource)

  return {
    label,
    floor: floor || undefined,
    source: source || undefined
  }
}

const requestTargetFocus = (target: Partial<RouteTargetLocation> | null, fallbackName = route.value.target) => {
  if (!target?.poiId || !target.floorId) {
    targetFocusRequest.value = null
    return false
  }

  const floorLabel = target.floorLabel || guideFloorLabel(target.floorId)
  const focusTarget: RouteTargetLocation = {
    poiId: target.poiId,
    name: target.name || fallbackName,
    floorId: target.floorId,
    floorLabel,
    primaryCategoryZh: target.primaryCategoryZh,
    positionGltf: target.positionGltf
  }

  activeFloor.value = floorLabel
  routeViewState.value = 'preview'
  targetFocusRequestId.value += 1
  targetFocusRequest.value = toTargetFocusRequestViewModel(
    focusTarget,
    targetFocusRequestId.value
  )

  return true
}

const applyRouteOptions = async (options: Record<string, unknown> = {}) => {
  await loadGuideFloors()

  const facilityId = normalizeStringOption(options.facilityId)
  const target = normalizeStringOption(options.target)
  const state = normalizeRouteOption(options.state)
  const startLocation = createStartLocationFromOptions(options)

  if (!facilityId && !target) {
    hasRouteTarget.value = false
    route.value = initialLocationPreviewPlan()
    routeViewState.value = 'preview'
    activeFloor.value = defaultGuideFloorLabel()
    targetFocusRequest.value = null
    isManualLocationPanelOpen.value = false
    return false
  }

  const matchedPoi = facilityId ? await guideUseCase.getPoiById(facilityId) : null

  if (!matchedPoi) {
    hasRouteTarget.value = false
    route.value = await guideUseCase.createLocationPreviewPlan('', target || '目标地点', startLocation)
    routeViewState.value = 'preview'
    activeFloor.value = startLocation?.floor || defaultGuideFloorLabel()
    targetFocusRequest.value = null
    isManualLocationPanelOpen.value = false
    return false
  }

  route.value = await guideUseCase.createLocationPreviewPlan(
    facilityId,
    target || matchedPoi.name,
    startLocation
  )

  hasRouteTarget.value = true
  routeViewState.value = isRouteViewState(state) ? state : 'preview'
  isManualLocationPanelOpen.value = false

  if (routeViewState.value === 'preview') {
    requestTargetFocus(route.value.targetLocation, matchedPoi.name)
  } else {
    activeFloor.value = matchedPoi.floorLabel || guideFloorLabel(matchedPoi.floorId)
  }

  setLocationPreviewTitle()
  saveCurrentLocationPreviewUrl()

  return true
}

onLoad((options: any) => {
  setLocationPreviewTitle()
  void applyRouteOptions(options)
})

// #ifdef H5
const readH5RouteOptions = () => {
  const hash = window.location.hash || ''
  const queryStart = hash.indexOf('?')
  const query = queryStart >= 0 ? hash.slice(queryStart + 1) : window.location.search.slice(1)
  const params = new URLSearchParams(query)
  const options: Record<string, string> = {}

  params.forEach((value, key) => {
    options[key] = value
  })

  return options
}

const syncH5RouteOptions = () => {
  void applyRouteOptions(readH5RouteOptions())
}

onMounted(() => {
  syncH5RouteOptions()
  window.addEventListener('hashchange', syncH5RouteOptions)
})

onUnmounted(() => {
  window.removeEventListener('hashchange', syncH5RouteOptions)
})
// #endif

const handleSelectTarget = () => {
  uni.redirectTo({
    url: '/pages/search/index',
    fail: () => {
      uni.navigateTo({
        url: '/pages/search/index'
      })
    }
  })
}

const handleSearchTap = () => {
  uni.navigateTo({
    url: `/pages/search/index?keyword=${encodeURIComponent(route.value.target)}`
  })
}

const handleViewOutdoorMap = () => {
  isManualLocationPanelOpen.value = false
  routeViewState.value = 'outdoor-preview'
  saveCurrentLocationPreviewUrl()
}

const handleShowTargetLocation = (target: Partial<RouteTargetLocation> | null) => {
  const focused = requestTargetFocus(target || route.value.targetLocation)

  if (!focused) {
    uni.showToast({
      title: '目标暂无三维位置数据',
      icon: 'none'
    })
    return
  }

  uni.showToast({
    title: '已显示三维位置',
    icon: 'none'
  })
}

const handleTargetFocus = (result: TargetPoiFocusResult) => {
  if (result.status === 'focused') return

  uni.showToast({
    title: result.message || '目标位置暂时无法聚焦',
    icon: 'none'
  })
}

const handleReturnToPreview = () => {
  isManualLocationPanelOpen.value = false
  routeViewState.value = 'preview'
  requestTargetFocus(route.value.targetLocation)
  saveCurrentLocationPreviewUrl()
}

const handleRelocate = () => {
  isManualLocationPanelOpen.value = false
  routeViewState.value = 'preview'
}

const handleManualSelect = () => {
  isManualLocationPanelOpen.value = true
}

const handleManualFloorChange = (floor: string) => {
  selectedManualFloor.value = floor
}

const handleManualAreaChange = (area: string) => {
  selectedManualArea.value = area
}

const handleManualLocationCancel = () => {
  isManualLocationPanelOpen.value = false
}

const createManualStartLocation = (): RouteStartLocation => ({
  label: `${selectedManualFloor.value} ${selectedManualArea.value}`,
  floor: selectedManualFloor.value,
  source: 'manual'
})

const handleConfirmLocation = async () => {
  const startLocation = createManualStartLocation()

  route.value = await guideUseCase.createLocationPreviewPlan(
    route.value.facilityId,
    route.value.target,
    startLocation
  )
  activeFloor.value = startLocation.floor || activeFloor.value
  isManualLocationPanelOpen.value = false
  routeViewState.value = 'preview'
  requestTargetFocus(route.value.targetLocation)
  saveCurrentLocationPreviewUrl()
}

const handleModeChange = (mode: GuideMode) => {
  if (routeViewState.value === 'outdoor-preview' && mode === '3d') {
    routeViewState.value = 'preview'
    requestTargetFocus(route.value.targetLocation)
    saveCurrentLocationPreviewUrl()
    return
  }

  if (routeViewState.value !== 'outdoor-preview' && mode === '2d') {
    routeViewState.value = 'outdoor-preview'
    saveCurrentLocationPreviewUrl()
    return
  }

  console.log('位置预览切换导览模式:', mode)
}

const handleFloorChange = (floor: string) => {
  activeFloor.value = floor
  console.log('位置预览切换楼层:', floor)
}

const handleToolClick = (tool: string) => {
  if (tool === '重定') {
    routeViewState.value = 'location-error'
    return
  }

  uni.showToast({
    title: tool === '回正' ? '已回到三维总览' : guideUseCase.getRouteReadinessSnapshot().message,
    icon: 'none'
  })
}

const handleTopTabChange = (tab: GuideTopTab) => {
  navigateToGuideTopTab(tab)
}

const handlePageBack = () => {
  if (isManualLocationPanelOpen.value) {
    handleManualLocationCancel()
    return
  }

  uni.navigateBack({
    delta: 1,
    fail: () => {
      uni.reLaunch({
        url: '/pages/index/index'
      })
    }
  })
}

</script>

<style scoped lang="scss">
.target-required-card {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: calc(env(safe-area-inset-bottom) + 34px);
  min-height: 170px;
  padding: 22px 20px 20px;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  background: #ffffff;
  border: 0;
  border-radius: 20px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.14);
  z-index: 45;
}

.target-required-title {
  font-size: 18px;
  line-height: 24px;
  font-weight: 700;
  color: #000000;
}

.target-required-desc {
  margin-top: 10px;
  font-size: 13px;
  line-height: 19px;
  color: #5f666d;
}

.target-required-btn {
  margin-top: 22px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  background: #000000;
  border: 1px solid #000000;
  border-radius: 8px;
}

.target-required-btn-text {
  font-size: 14px;
  line-height: 19px;
  font-weight: 500;
  color: var(--museum-accent);
}

.outdoor-preview-actions,
.error-actions {
  display: flex;
}

.outdoor-preview-btn,
.dialog-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  border-radius: 8px;
}

.outdoor-preview-btn-text,
.dialog-btn-text {
  font-size: 14px;
  line-height: 19px;
  font-weight: 500;
  color: #000000;
}

.outdoor-preview-btn.primary .outdoor-preview-btn-text,
.dialog-btn.primary .dialog-btn-text {
  color: var(--museum-accent);
}

.location-error-dialog {
  position: absolute;
  top: 200px;
  left: 24px;
  width: 327px;
  height: 288px;
  padding: 28px 24px 36px;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-sizing: border-box;
  background: #ffffff;
  border-radius: 18px;
  box-shadow: 0 14px 28px rgba(0, 0, 0, 0.18);
  z-index: 55;
}

.error-icon {
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000000;
  border-radius: 32px;
}

.error-icon-mark {
  font-size: 34px;
  line-height: 40px;
  font-weight: 700;
  color: var(--museum-accent);
}

.error-title {
  margin-top: 18px;
  font-size: 20px;
  line-height: 27px;
  font-weight: 700;
  color: #000000;
}

.error-desc {
  width: 258px;
  margin-top: 10px;
  font-size: 13px;
  line-height: 20px;
  color: #626970;
  text-align: center;
}

.error-actions {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: 36px;
  gap: 16px;
}

.dialog-btn {
  width: 132px;
  height: 40px;
}

.dialog-btn.secondary {
  background: #ffffff;
  border: 1px solid #000000;
}

.dialog-btn.primary {
  background: #000000;
  border: 1px solid #000000;
}

.manual-location-sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(env(safe-area-inset-bottom) + 34px);
  height: 196px;
  padding: 21px 20px 14px;
  box-sizing: border-box;
  background: #ffffff;
  border-radius: 20px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.14);
  z-index: 54;
}

.manual-title {
  display: block;
  font-size: 18px;
  line-height: 24px;
  font-weight: 700;
  color: #000000;
}

.chip-row {
  display: flex;
  align-items: center;
  gap: 8px;
  overflow: hidden;
}

.floor-chip-row {
  margin-top: 17px;
}

.area-chip-row {
  margin-top: 14px;
}

.manual-chip {
  height: 28px;
  min-width: 48px;
  padding: 0 13px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  background: #f4f4ef;
  border: 1px solid #e7e8e2;
  border-radius: 14px;
}

.manual-chip.area {
  min-width: 72px;
}

.manual-chip.active {
  background: #000000;
  border-color: #000000;
}

.manual-chip-text {
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  color: #60666d;
}

.manual-chip.active .manual-chip-text {
  color: var(--museum-accent);
}

.manual-location-actions {
  position: absolute;
  left: 20px;
  right: 20px;
  bottom: 14px;
  display: flex;
  gap: 10px;
}

.manual-location-btn {
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  border-radius: 8px;
}

.manual-location-btn.secondary {
  width: 82px;
  background: #ffffff;
  border: 1px solid #000000;
}

.manual-location-btn.primary {
  flex: 1;
  min-width: 0;
  background: #000000;
  border: 1px solid #000000;
}

.manual-location-btn-text {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  line-height: 19px;
  font-weight: 500;
  color: #000000;
}

.manual-location-btn.primary .manual-location-btn-text {
  color: var(--museum-accent);
}

.entrance-tip {
  position: absolute;
  top: 154px;
  left: 30px;
  width: 168px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  background: #ffffff;
  border: 1px solid #e3e6df;
  border-radius: 10px;
  box-shadow: 0 8px 18px rgba(80, 98, 88, 0.12);
  z-index: 45;
}

.entrance-tip-text {
  font-size: 14px;
  line-height: 19px;
  font-weight: 600;
  color: #1f2329;
}

.outdoor-preview-card {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: calc(env(safe-area-inset-bottom) + 34px);
  height: 158px;
  padding: 18px 16px 14px;
  box-sizing: border-box;
  background: #ffffff;
  border-radius: 18px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.14);
  z-index: 45;
}

.outdoor-preview-title {
  display: block;
  font-size: 18px;
  line-height: 24px;
  font-weight: 700;
  color: #000000;
}

.outdoor-preview-desc {
  display: block;
  margin-top: 10px;
  font-size: 13px;
  line-height: 20px;
  color: #626970;
}

.outdoor-preview-actions {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 14px;
  gap: 12px;
}

.outdoor-preview-btn {
  height: 40px;
}

.outdoor-preview-btn.secondary {
  width: 126px;
  background: #ffffff;
  border: 1px solid #000000;
}

.outdoor-preview-btn.primary {
  width: 181px;
  background: #000000;
  border: 1px solid #000000;
}
</style>
