<script setup lang="ts">
import type { PoiSummary } from '@/domain/poi'
import type { BudgetLevel, Interest, Pace, PlanningRequest, Theme, Transport, TravelPreferences } from '@/domain/travel'
import { getPoiRepository } from '@/data/poi'
import { TravelValidationError } from '@/domain/travel'
import { isLocationSnapshotFresh } from '@/services/location'
import { createPlan, loadPlanningRequest, loadPreferences, savePlan, savePreferences } from '@/services/travel-assistant'
import { useLocationStore } from '@/stores'

interface PlannerPreset {
  themes?: unknown
  interests?: unknown
  extraRequirements?: unknown
}

const PRESET_STORAGE_KEY = 'guangming:planner-preset'
const MIN_DURATION_MINUTES = 2 * 60
const MAX_DURATION_MINUTES = 10 * 60
const DURATION_STEP_MINUTES = 30
const MIN_CUSTOM_POIS = 2
const MAX_CUSTOM_POIS = 8
const LOCATION_MAX_AGE_MS = 5 * 60 * 1000
const themeOptions: Theme[] = ['亲子', '情侣', '朋友', '银发', '研学']
const interestOptions: Interest[] = ['自然风光', '文化场馆', '生态科普', '美食', '摄影']
const paceOptions: Array<{ label: string, description: string, value: Pace }> = [
  { label: '轻松', description: '少走慢游', value: 'relaxed' },
  { label: '适中', description: '均衡安排', value: 'moderate' },
  { label: '紧凑', description: '尽量多看', value: 'compact' },
]
const transportOptions: Array<{ label: string, value: Transport }> = [
  { label: '公共交通', value: 'public_transport' },
  { label: '自驾', value: 'driving' },
  { label: '步行', value: 'walking' },
]
const budgetOptions: Array<{ label: string, value: BudgetLevel }> = [
  { label: '经济', value: 'economy' },
  { label: '适中', value: 'standard' },
  { label: '品质', value: 'quality' },
]

const form = reactive<TravelPreferences>({
  destination: '深圳市光明区',
  themes: ['亲子'],
  duration: 'half_day',
  pace: 'relaxed',
  interests: ['自然风光'],
  adults: 2,
  children: 0,
  childAges: [],
  transport: 'public_transport',
  budgetLevel: 'standard',
  extraRequirements: '',
})

const planningMode = ref<PlanningRequest['mode']>('quick')
const durationMinutes = ref(4 * 60)
const selectedPoiIds = ref<string[]>([])
const customStartsFromCurrentLocation = ref(false)
const locationFreshnessCheckedAt = ref(Date.now())
const submitting = ref(false)
let locationFreshnessTimer: ReturnType<typeof setTimeout> | null = null
let pageVisible = false
const locationStore = useLocationStore()
const locationAvailable = computed(() => {
  const snapshot = locationStore.snapshot
  return locationStore.status === 'ready'
    && Boolean(snapshot)
    && isLocationSnapshotFresh(
      snapshot?.capturedAt ?? '',
      locationFreshnessCheckedAt.value,
      LOCATION_MAX_AGE_MS,
    )
})
const poiSummaries = shallowRef<PoiSummary[]>(getPoiRepository().getPoiSummaries())
const poiGroups = computed(() => {
  const groups = new Map<string, PoiSummary[]>()
  poiSummaries.value.forEach((poi) => {
    const current = groups.get(poi.categoryName) ?? []
    current.push(poi)
    groups.set(poi.categoryName, current)
  })
  return [...groups.entries()].map(([categoryName, pois]) => ({ categoryName, pois }))
})
const durationText = computed(() => {
  const hours = Math.floor(durationMinutes.value / 60)
  const minutes = durationMinutes.value % 60
  return minutes ? `${hours} 小时 ${minutes} 分钟` : `${hours} 小时`
})
const usesCurrentLocation = computed(() => locationAvailable.value
  && (planningMode.value === 'quick' || customStartsFromCurrentLocation.value))
const planningEngineLabel = '本地 POC 规划'
const locationActionText = computed(() => {
  if (locationStore.status === 'locating')
    return '定位中…'
  if (locationStore.status === 'denied')
    return '开启位置权限'
  return locationStore.snapshot ? '刷新当前位置' : '获取当前位置'
})
const locationDescription = computed(() => {
  if (locationStore.status === 'locating')
    return '正在获取位置，请稍候。'
  if (locationStore.status === 'denied') {
    return planningMode.value === 'quick'
      ? '位置权限未开启；请前往设置授权，或改用自选点位规划。'
      : '位置权限未开启；自选点位规划仍可继续使用。'
  }
  if (planningMode.value === 'quick') {
    return locationAvailable.value
      ? '将从当前位置出发并优先安排较近点位；可随时刷新。'
      : locationStore.snapshot
        ? '上次定位已超过 5 分钟，请刷新后再开始快速规划。'
        : '快速规划需要先主动获取当前位置。'
  }
  if (!locationAvailable.value)
    return '可选获取位置，用于优化自选点位的起点和顺序。'
  return customStartsFromCurrentLocation.value
    ? '已将当前位置设为自选路线起点。'
    : '位置已就绪；如需从当前位置出发，请开启下方选项。'
})

function assignPreferences(preferences: TravelPreferences) {
  Object.assign(form, {
    ...preferences,
    themes: [...preferences.themes],
    interests: [...preferences.interests],
    childAges: [...preferences.childAges],
  })
}

function isStringArray<T extends string>(value: unknown, allowed: readonly T[]): value is T[] {
  return Array.isArray(value) && value.every(item => typeof item === 'string' && allowed.includes(item as T))
}

function applyPreset() {
  let preset: PlannerPreset | null = null
  try {
    const value = uni.getStorageSync(PRESET_STORAGE_KEY)
    preset = value && typeof value === 'object' ? value as PlannerPreset : null
    uni.removeStorageSync(PRESET_STORAGE_KEY)
  }
  catch {
    preset = null
  }

  if (!preset)
    return
  if (isStringArray(preset.themes, themeOptions))
    form.themes = [...preset.themes]
  if (isStringArray(preset.interests, interestOptions))
    form.interests = [...preset.interests]
  if (typeof preset.extraRequirements === 'string')
    form.extraRequirements = preset.extraRequirements.slice(0, 500)
}

function toggleTheme(theme: Theme) {
  const index = form.themes.indexOf(theme)
  if (index >= 0)
    form.themes.splice(index, 1)
  else
    form.themes.push(theme)
}

function toggleInterest(interest: Interest) {
  const index = form.interests.indexOf(interest)
  if (index >= 0)
    form.interests.splice(index, 1)
  else
    form.interests.push(interest)
}

function setPlanningMode(mode: PlanningRequest['mode']) {
  planningMode.value = mode
}

function togglePoi(poiId: string) {
  const index = selectedPoiIds.value.indexOf(poiId)
  if (index >= 0) {
    selectedPoiIds.value.splice(index, 1)
    return
  }
  if (selectedPoiIds.value.length >= MAX_CUSTOM_POIS) {
    uni.showToast({ title: `最多选择 ${MAX_CUSTOM_POIS} 个点位`, icon: 'none' })
    return
  }
  selectedPoiIds.value.push(poiId)
}

function changeDuration(event: { detail: { value: number } }) {
  const value = Number(event.detail.value)
  if (!Number.isFinite(value))
    return
  durationMinutes.value = Math.min(
    MAX_DURATION_MINUTES,
    Math.max(MIN_DURATION_MINUTES, Math.round(value / DURATION_STEP_MINUTES) * DURATION_STEP_MINUTES),
  )
}

function changeCustomOrigin(event: Event) {
  const value = (event as Event & { detail?: { value?: unknown } }).detail?.value
  customStartsFromCurrentLocation.value = Boolean(value)
}

function changeAdults(delta: number) {
  form.adults = Math.min(20, Math.max(0, form.adults + delta))
}

function changeChildren(delta: number) {
  const next = Math.min(20, Math.max(0, form.children + delta))
  while (form.childAges.length < next)
    form.childAges.push(6)
  form.childAges.splice(next)
  form.children = next
}

function setAge(index: number, event: { detail: { value: number } }) {
  form.childAges[index] = event.detail.value
}

function isPermissionDenied(error: unknown): boolean {
  if (!error || typeof error !== 'object')
    return false
  const message = String((error as { errMsg?: unknown }).errMsg ?? '')
  return /auth deny|auth denied|authorize:fail|permission/i.test(message)
}

function clearLocationFreshnessTimer() {
  if (locationFreshnessTimer === null)
    return
  clearTimeout(locationFreshnessTimer)
  locationFreshnessTimer = null
}

function refreshLocationFreshness() {
  clearLocationFreshnessTimer()
  const checkedAt = Date.now()
  locationFreshnessCheckedAt.value = checkedAt
  const snapshot = locationStore.snapshot
  if (locationStore.status !== 'ready' || !snapshot)
    return
  if (!pageVisible)
    return

  const capturedAt = Date.parse(snapshot.capturedAt)
  if (!Number.isFinite(capturedAt) || capturedAt > checkedAt)
    return
  const expiresInMs = capturedAt + LOCATION_MAX_AGE_MS - checkedAt
  if (expiresInMs < 0)
    return
  locationFreshnessTimer = setTimeout(refreshLocationFreshness, expiresInMs + 1)
}

function requestCurrentLocation() {
  if (locationStore.status === 'locating')
    return
  if (locationStore.status === 'denied') {
    uni.openSetting({
      success: (settings) => {
        if (settings.authSetting['scope.userLocation'])
          locateFromUserAction()
      },
    })
    return
  }
  locateFromUserAction()
}

function locateFromUserAction() {
  locationStore.startLocating()
  refreshLocationFreshness()
  uni.getLocation({
    type: 'gcj02',
    isHighAccuracy: true,
    highAccuracyExpireTime: 8000,
    success: (result) => {
      const updated = locationStore.updateLocation(
        Number(result.longitude),
        Number(result.latitude),
        Number(result.accuracy ?? result.horizontalAccuracy),
      )
      if (!updated) {
        locationStore.failLocation('error', '定位结果无效，请稍后重试')
        uni.showToast({ title: '定位结果无效', icon: 'none' })
        return
      }
      refreshLocationFreshness()
      if (planningMode.value === 'custom')
        customStartsFromCurrentLocation.value = true
      uni.showToast({ title: '已更新当前位置', icon: 'success' })
    },
    fail: (error) => {
      if (isPermissionDenied(error)) {
        locationStore.failLocation('denied', '位置权限未开启')
        uni.showModal({
          title: '需要位置权限',
          content: '开启后可从当前位置出发规划路线，也可不授权并使用全域规划。',
          confirmText: '去设置',
          success: result => result.confirm && requestCurrentLocation(),
        })
        return
      }
      locationStore.failLocation('unavailable', '暂时无法获取位置')
      uni.showToast({ title: '定位失败，可继续使用全域规划', icon: 'none' })
    },
  })
}

function showDurationFitNotice(status: 'overflow' | 'underfilled'): Promise<void> {
  return new Promise((resolve) => {
    uni.showModal({
      title: status === 'overflow' ? '时间不足' : '行程时间提示',
      content: status === 'overflow'
        ? '当前点位与行程安排无法在所选时长内完整体验，请在查看路线后适当精简点位或延长时长。'
        : '当前规划路径与所需时间差距较大，请结合实际交通、现场开放情况和个人节奏调整行程。',
      showCancel: false,
      confirmText: '查看路线',
      complete: () => resolve(),
    })
  })
}

async function submit() {
  refreshLocationFreshness()
  if (!form.themes.length && !form.interests.length) {
    uni.showToast({ title: '请至少选择一项主题或偏好', icon: 'none' })
    return
  }
  if (form.adults + form.children < 1) {
    uni.showToast({ title: '出行总人数至少为 1', icon: 'none' })
    return
  }
  if (planningMode.value === 'custom'
    && (selectedPoiIds.value.length < MIN_CUSTOM_POIS || selectedPoiIds.value.length > MAX_CUSTOM_POIS)) {
    uni.showToast({ title: `请选择 ${MIN_CUSTOM_POIS}-${MAX_CUSTOM_POIS} 个点位`, icon: 'none' })
    return
  }
  if (planningMode.value === 'quick' && !locationAvailable.value) {
    uni.showModal({
      title: '请先获取当前位置',
      content: '当前位置快速规划需要一次近期定位；如暂不授权，可改用自选点位规划。',
      confirmText: locationStore.status === 'denied' ? '去设置' : '立即定位',
      cancelText: '暂不使用',
      success: result => result.confirm && requestCurrentLocation(),
    })
    return
  }

  submitting.value = true
  try {
    const preferences: TravelPreferences = {
      ...form,
      duration: durationMinutes.value <= 4 * 60 ? 'half_day' : 'full_day',
      themes: [...form.themes],
      interests: [...form.interests],
      childAges: [...form.childAges],
    }
    const snapshot = locationStore.snapshot
    const origin = usesCurrentLocation.value && snapshot
      ? {
          longitude: snapshot.longitude,
          latitude: snapshot.latitude,
          coordinateSystem: snapshot.coordinateSystem,
        }
      : undefined
    const request: PlanningRequest = {
      mode: planningMode.value,
      preferences,
      durationMinutes: durationMinutes.value,
      selectedPoiIds: planningMode.value === 'custom' ? [...selectedPoiIds.value] : [],
      origin,
    }
    const response = await createPlan(request)
    savePreferences(preferences)
    savePlan(response)
    if (response.itinerary.durationFitStatus === 'overflow' || response.itinerary.durationFitStatus === 'underfilled')
      await showDurationFitNotice(response.itinerary.durationFitStatus)
    uni.navigateTo({ url: '/pages/itinerary/index' })
  }
  catch (error) {
    const quickRouteUnreachable = error instanceof TravelValidationError
      && error.code === 'quick_route_unreachable'
    uni.showModal({
      title: quickRouteUnreachable ? '当前路线无法完成' : '暂时无法生成路线',
      content: quickRouteUnreachable
        ? '当前起点、交通方式与所选时长无法容纳首个点位。请靠近光明区、延长游玩时长，或改用自选点位规划。'
        : error instanceof Error ? error.message : '请检查选择后重试',
      showCancel: false,
    })
  }
  finally {
    submitting.value = false
  }
}

onShow(() => {
  pageVisible = true
  refreshLocationFreshness()
  const storedRequest = loadPlanningRequest()
  const storedPreferences = storedRequest?.preferences ?? loadPreferences()
  if (storedPreferences)
    assignPreferences(storedPreferences)
  if (storedRequest) {
    planningMode.value = storedRequest.mode
    durationMinutes.value = storedRequest.durationMinutes
    selectedPoiIds.value = [...storedRequest.selectedPoiIds]
    customStartsFromCurrentLocation.value = false
  }
  else if (storedPreferences) {
    durationMinutes.value = storedPreferences.duration === 'half_day' ? 4 * 60 : 8 * 60
  }
  applyPreset()
})

function deactivatePlannerPage() {
  pageVisible = false
  clearLocationFreshnessTimer()
}

onHide(deactivatePlannerPage)
onUnload(deactivatePlannerPage)
</script>

<template>
  <view class="planner-page">
    <view class="planner-intro">
      <view class="planner-intro__icon">
        <text>智游</text>
      </view>
      <view class="planner-intro__bubble">
        <text class="planner-intro__title">定制你的光明文旅路线</text>
        <text class="planner-intro__copy">可以一键生成推荐路线，也可先选好想去的点位再编排。</text>
        <view class="planner-intro__mode">
          <view class="planner-intro__dot" />
          {{ planningEngineLabel }}
        </view>
      </view>
    </view>

    <view class="planner-form">
      <view class="planner-form__header">
        <view>
          <text class="planner-form__title">出行画像</text>
          <text class="planner-form__subtitle">选择会作为路线编排依据</text>
        </view>
        <text class="planner-form__district">深圳 · 光明</text>
      </view>

      <view class="form-section planning-mode-section">
        <view class="form-section__heading">
          <text class="form-section__label">规划模式</text>
        </view>
        <view class="planning-mode-grid">
          <button
            class="planning-mode-card"
            :class="{ 'planning-mode-card--active': planningMode === 'quick' }"
            @click="setPlanningMode('quick')"
          >
            <text class="planning-mode-card__title">当前位置快速规划</text>
            <text class="planning-mode-card__copy">根据时长和偏好智能选点</text>
          </button>
          <button
            class="planning-mode-card"
            :class="{ 'planning-mode-card--active': planningMode === 'custom' }"
            @click="setPlanningMode('custom')"
          >
            <text class="planning-mode-card__title">自选点位规划</text>
            <text class="planning-mode-card__copy">从全部 POI 中选 2-8 个编排</text>
          </button>
        </view>
        <text class="planning-mode-tip">本期由本机完成选点、顺序、交通估算与时长核算。</text>
      </view>

      <view class="planning-origin" :class="{ 'planning-origin--ready': locationAvailable }">
        <view class="planning-origin__heading">
          <text class="planning-origin__title">
            {{ usesCurrentLocation ? '已使用当前位置' : '当前位置为可选项' }}
          </text>
          <button :disabled="locationStore.status === 'locating'" @click="requestCurrentLocation">
            {{ locationActionText }}
          </button>
        </view>
        <text class="planning-origin__copy">{{ locationDescription }}</text>
        <view v-if="planningMode === 'custom' && locationAvailable" class="origin-switch">
          <text>自选路线从当前位置出发</text>
          <switch
            :checked="customStartsFromCurrentLocation"
            color="#167a5b"
            @change="changeCustomOrigin"
          />
        </view>
        <text class="planning-origin__privacy">定位仅由你主动触发，精确坐标不写入本机路线缓存。</text>
      </view>

      <view v-if="planningMode === 'custom'" class="form-section custom-poi-section">
        <view class="form-section__heading custom-poi-heading">
          <view>
            <text class="form-section__label">选择想去的点位</text>
            <text class="form-section__hint">至少 2 个，最多 8 个</text>
          </view>
          <text class="custom-poi-count" :class="{ 'custom-poi-count--ready': selectedPoiIds.length >= MIN_CUSTOM_POIS }">
            已选 {{ selectedPoiIds.length }}/{{ MAX_CUSTOM_POIS }}
          </text>
        </view>
        <scroll-view class="poi-picker" scroll-y enhanced show-scrollbar>
          <view v-for="group in poiGroups" :key="group.categoryName" class="poi-group">
            <text class="poi-group__title">{{ group.categoryName }} · {{ group.pois.length }}</text>
            <button
              v-for="poi in group.pois"
              :key="poi.id"
              class="poi-option"
              :class="{ 'poi-option--active': selectedPoiIds.includes(poi.id) }"
              @click="togglePoi(poi.id)"
            >
              <view class="poi-option__content">
                <text class="poi-option__name">{{ poi.name }}</text>
                <text class="poi-option__meta">{{ poi.categoryName }} · 推荐 {{ poi.recommendationIndex }}/5</text>
              </view>
              <text class="poi-option__check">
                {{ selectedPoiIds.includes(poi.id) ? selectedPoiIds.indexOf(poi.id) + 1 : '＋' }}
              </text>
            </button>
          </view>
        </scroll-view>
      </view>

      <view class="form-section">
        <view class="form-section__heading">
          <text class="form-section__label">{{ planningMode === 'quick' ? '出行主题' : '路线偏好' }}</text>
          <text class="form-section__hint">多选</text>
        </view>
        <view class="option-grid option-grid--themes">
          <button
            v-for="theme in themeOptions"
            :key="theme"
            class="option"
            :class="{ 'option--active': form.themes.includes(theme) }"
            @click="toggleTheme(theme)"
          >
            <text v-if="form.themes.includes(theme)" class="option__check">✓</text>
            {{ theme }}
          </button>
        </view>
      </view>

      <view class="form-section">
        <view class="form-section__heading duration-heading">
          <text class="form-section__label">游玩时长</text>
          <text class="duration-value">{{ durationText }}</text>
        </view>
        <slider
          :value="durationMinutes"
          :min="MIN_DURATION_MINUTES"
          :max="MAX_DURATION_MINUTES"
          :step="DURATION_STEP_MINUTES"
          active-color="#167a5b"
          background-color="#dce8e2"
          block-color="#167a5b"
          block-size="22"
          @change="changeDuration"
        />
        <view class="duration-scale">
          <text>2 小时</text>
          <text>30 分钟步进</text>
          <text>10 小时</text>
        </view>
      </view>

      <view class="form-section">
        <view class="form-section__heading">
          <text class="form-section__label">行程节奏</text>
        </view>
        <view class="option-grid option-grid--three">
          <button
            v-for="pace in paceOptions"
            :key="pace.value"
            class="option option--stack"
            :class="{ 'option--active': form.pace === pace.value }"
            @click="form.pace = pace.value"
          >
            <text>{{ pace.label }}</text><text class="option__sub">{{ pace.description }}</text>
          </button>
        </view>
      </view>

      <view class="form-section">
        <view class="form-section__heading">
          <text class="form-section__label">特色偏好</text>
          <text class="form-section__hint">多选</text>
        </view>
        <view class="option-grid option-grid--interests">
          <button
            v-for="interest in interestOptions"
            :key="interest"
            class="option"
            :class="{ 'option--active': form.interests.includes(interest) }"
            @click="toggleInterest(interest)"
          >
            <text v-if="form.interests.includes(interest)" class="option__check">✓</text>
            {{ interest }}
          </button>
        </view>
      </view>

      <view class="form-section">
        <view class="form-section__heading">
          <text class="form-section__label">出行方式</text>
        </view>
        <view class="option-grid option-grid--three">
          <button
            v-for="transport in transportOptions"
            :key="transport.value"
            class="option"
            :class="{ 'option--active': form.transport === transport.value }"
            @click="form.transport = transport.value"
          >
            {{ transport.label }}
          </button>
        </view>
      </view>

      <view class="form-section">
        <view class="form-section__heading">
          <text class="form-section__label">预算偏好</text>
        </view>
        <view class="option-grid option-grid--three">
          <button
            v-for="budget in budgetOptions"
            :key="budget.value"
            class="option"
            :class="{ 'option--active': form.budgetLevel === budget.value }"
            @click="form.budgetLevel = budget.value"
          >
            {{ budget.label }}
          </button>
        </view>
      </view>

      <view class="form-section">
        <view class="form-section__heading">
          <text class="form-section__label">同行人数</text>
          <text class="form-section__hint">儿童年龄会影响推荐</text>
        </view>
        <view class="people-grid">
          <view class="counter-card">
            <view><text class="counter-card__label">成人</text><text class="counter-card__hint">18 岁以上</text></view>
            <view class="counter">
              <button @click="changeAdults(-1)">−</button><text>{{ form.adults }}</text><button @click="changeAdults(1)">＋</button>
            </view>
          </view>
          <view class="counter-card">
            <view><text class="counter-card__label">儿童</text><text class="counter-card__hint">0–17 岁</text></view>
            <view class="counter">
              <button @click="changeChildren(-1)">−</button><text>{{ form.children }}</text><button @click="changeChildren(1)">＋</button>
            </view>
          </view>
        </view>
        <view v-if="form.children" class="ages">
          <view v-for="(_, index) in form.childAges" :key="index" class="age-row">
            <text>儿童 {{ index + 1 }}</text>
            <slider
              :value="form.childAges[index]"
              min="0"
              max="17"
              active-color="#167a5b"
              background-color="#dce8e2"
              block-color="#167a5b"
              block-size="18"
              @change="setAge(index, $event)"
            />
            <text class="age-row__value">{{ form.childAges[index] }} 岁</text>
          </view>
        </view>
      </view>

      <view class="form-section form-section--last">
        <view class="form-section__heading">
          <text class="form-section__label">补充说明</text>
          <text class="form-section__hint">选填</text>
        </view>
        <view class="textarea-wrap">
          <textarea
            v-model="form.extraRequirements"
            class="textarea"
            maxlength="500"
            placeholder="例如：下午下雨，只安排室内项目；带婴儿车，不爬山…"
            placeholder-class="textarea__placeholder"
          />
          <text class="textarea__count">{{ form.extraRequirements.length }}/500</text>
        </view>
      </view>

      <button class="planner-submit" :disabled="submitting" @click="submit">
        <text class="planner-submit__spark">✦</text>
        <text>{{ submitting ? '正在编排路线…' : planningMode === 'quick' ? '开始快速规划' : `编排 ${selectedPoiIds.length} 个自选点位` }}</text>
      </button>
    </view>

    <text class="planner-footer">
      匿名使用 · 本期不调用 AI · 路线与偏好仅保存在本机
    </text>
  </view>
</template>

<route lang="yaml">
layout: map
style:
  navigationBarTitleText: 智能路线规划
  navigationBarBackgroundColor: '#ffffff'
  navigationBarTextStyle: black
  backgroundColor: '#f4f8f6'
</route>

<style scoped lang="scss">
.planner-page {
  min-height: 100vh;
  padding: 28rpx 20rpx calc(50rpx + env(safe-area-inset-bottom));
  color: #17221d;
  background: #f4f8f6;
}

.planner-intro {
  display: flex;
  gap: 18rpx;
  align-items: flex-end;
  padding: 20rpx 10rpx 30rpx;
}

.planner-intro__icon {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 124rpx;
  height: 124rpx;
  font-size: 21rpx;
  font-weight: 800;
  color: #22e2b6;
  background: #0b3933;
  border: 8rpx solid #d9f4e7;
  border-radius: 42rpx;
  box-shadow: 0 14rpx 30rpx rgb(5 112 68 / 15%);
}

.planner-intro__bubble {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 24rpx;
  background: #fff;
  border: 1rpx solid #d4e8df;
  border-radius: 24rpx;
  box-shadow: 0 12rpx 32rpx rgb(20 80 54 / 7%);
}

.planner-intro__title {
  font-size: 28rpx;
  font-weight: 800;
  line-height: 40rpx;
  color: #087846;
}

.planner-intro__copy {
  margin-top: 8rpx;
  font-size: 20rpx;
  line-height: 32rpx;
  color: #64716b;
}

.planner-intro__mode {
  display: flex;
  gap: 8rpx;
  align-items: center;
  margin-top: 12rpx;
  font-size: 17rpx;
  color: #718078;
}

.planner-intro__dot {
  width: 11rpx;
  height: 11rpx;
  background: #08a865;
  border-radius: 50%;
  box-shadow: 0 0 0 5rpx rgb(8 168 101 / 12%);
}

.planner-form {
  padding: 0 28rpx 32rpx;
  background: #fff;
  border: 1rpx solid #d9e6e0;
  border-radius: 28rpx;
  box-shadow: 0 20rpx 60rpx rgb(19 75 50 / 8%);
}

.planner-form__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 28rpx 0;
  border-bottom: 1rpx solid #e9efec;
}

.planner-form__title,
.planner-form__subtitle,
.planner-form__district {
  display: block;
}

.planning-origin {
  padding: 18rpx 20rpx;
  margin-top: 22rpx;
  color: #68766f;
  background: #f4f7f5;
  border: 1rpx solid #e1e8e4;
  border-radius: 14rpx;
}

.planning-mode-section {
  padding-top: 26rpx;
}

.planning-mode-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14rpx;
}

.planning-mode-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-height: 140rpx;
  padding: 20rpx;
  margin: 0;
  line-height: 1.45;
  text-align: left;
  background: #f7faf8;
  border: 2rpx solid #dce7e1;
  border-radius: 18rpx;
}

.planning-mode-card::after,
.planning-origin button::after,
.poi-option::after {
  border: 0;
}

.planning-mode-card--active {
  color: #fff;
  background: linear-gradient(145deg, #087f51, #086a47);
  border-color: #08784c;
  box-shadow: 0 10rpx 24rpx rgb(5 118 69 / 18%);
}

.planning-mode-card__title {
  font-size: 23rpx;
  font-weight: 800;
  line-height: 34rpx;
}

.planning-mode-card__copy {
  margin-top: 8rpx;
  font-size: 17rpx;
  line-height: 27rpx;
  color: #77857e;
}

.planning-mode-card--active .planning-mode-card__copy {
  color: rgb(255 255 255 / 75%);
}

.planning-mode-tip {
  display: block;
  margin-top: 14rpx;
  font-size: 18rpx;
  line-height: 30rpx;
  color: #7a6750;
}

.planning-origin__heading {
  display: flex;
  gap: 16rpx;
  align-items: center;
  justify-content: space-between;
}

.planning-origin__heading button {
  flex: none;
  width: auto;
  height: 54rpx;
  padding: 0 18rpx;
  margin: 0;
  font-size: 18rpx;
  line-height: 54rpx;
  color: #167a5b;
  background: #fff;
  border: 1rpx solid #cfe0d8;
  border-radius: 27rpx;
}

.planning-origin__heading button[disabled] {
  opacity: 0.55;
}

.planning-origin--ready {
  color: #245641;
  background: #eaf7f0;
  border-color: #cde8da;
}

.planning-origin__title,
.planning-origin__copy {
  display: block;
}

.origin-switch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 14rpx;
  margin-top: 14rpx;
  font-size: 19rpx;
  border-top: 1rpx solid #d9e8e0;
}

.origin-switch switch {
  transform: scale(0.78);
  transform-origin: right center;
}

.planning-origin__privacy {
  display: block;
  margin-top: 12rpx;
  font-size: 16rpx;
  line-height: 26rpx;
  color: #8a9690;
}

.custom-poi-section {
  padding-bottom: 22rpx;
}

.custom-poi-heading {
  justify-content: space-between;
}

.custom-poi-heading > view {
  display: flex;
  flex-direction: column;
  gap: 5rpx;
}

.custom-poi-count {
  flex: none;
  padding: 7rpx 13rpx;
  font-size: 18rpx;
  color: #65726b;
  background: #edf2ef;
  border-radius: 999rpx;
}

.custom-poi-count--ready {
  color: #08794b;
  background: #e4f5ec;
}

.poi-picker {
  box-sizing: border-box;
  height: 620rpx;
  padding: 4rpx 16rpx 16rpx;
  background: #f7faf8;
  border: 1rpx solid #dfe9e4;
  border-radius: 18rpx;
}

.poi-group {
  padding-top: 18rpx;
}

.poi-group__title {
  display: block;
  margin: 0 4rpx 10rpx;
  font-size: 18rpx;
  font-weight: 700;
  color: #6b7972;
}

.poi-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 86rpx;
  padding: 13rpx 15rpx;
  margin: 0 0 10rpx;
  color: #27332d;
  text-align: left;
  background: #fff;
  border: 1rpx solid #e0e8e4;
  border-radius: 14rpx;
}

.poi-option--active {
  color: #075f3d;
  background: #e9f7f0;
  border-color: #87c7aa;
}

.poi-option__content {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

.poi-option__name {
  overflow: hidden;
  font-size: 21rpx;
  font-weight: 700;
  line-height: 31rpx;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.poi-option__meta {
  margin-top: 3rpx;
  font-size: 16rpx;
  line-height: 25rpx;
  color: #849089;
}

.poi-option__check {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 42rpx;
  height: 42rpx;
  margin-left: 16rpx;
  font-size: 20rpx;
  font-weight: 800;
  color: #128056;
  background: #eef6f2;
  border-radius: 50%;
}

.poi-option--active .poi-option__check {
  color: #fff;
  background: #138056;
}

.duration-heading {
  justify-content: space-between;
}

.duration-value {
  padding: 7rpx 13rpx;
  font-size: 20rpx;
  font-weight: 800;
  color: #08794b;
  background: #e7f6ee;
  border-radius: 999rpx;
}

.duration-scale {
  display: flex;
  justify-content: space-between;
  font-size: 17rpx;
  color: #89958f;
}

.planning-origin__title {
  font-size: 22rpx;
  font-weight: 700;
  line-height: 32rpx;
}

.planning-origin__copy {
  margin-top: 4rpx;
  font-size: 18rpx;
  line-height: 30rpx;
}

.planner-form__title {
  font-size: 31rpx;
  font-weight: 800;
}

.planner-form__subtitle {
  margin-top: 5rpx;
  font-size: 18rpx;
  color: #8b9892;
}

.planner-form__district {
  padding: 8rpx 14rpx;
  font-size: 17rpx;
  font-weight: 700;
  color: #07814d;
  background: #e9f7f0;
  border-radius: 999rpx;
}

.form-section {
  padding: 32rpx 0;
  border-bottom: 1rpx solid #edf1ef;
}

.form-section--last {
  border-bottom: 0;
}

.form-section__heading {
  display: flex;
  gap: 12rpx;
  align-items: center;
  margin-bottom: 20rpx;
}

.form-section__label {
  font-size: 25rpx;
  font-weight: 700;
}

.form-section__hint {
  font-size: 19rpx;
  color: #9aa49f;
}

.option-grid {
  display: grid;
  gap: 14rpx;
}

.option-grid--themes {
  grid-template-columns: repeat(3, 1fr);
}

.option-grid--interests {
  grid-template-columns: repeat(2, 1fr);
}

.option-grid--two {
  grid-template-columns: repeat(2, 1fr);
}

.option-grid--three {
  grid-template-columns: repeat(3, 1fr);
}

.option {
  display: flex;
  gap: 6rpx;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 72rpx;
  padding: 10rpx 8rpx;
  margin: 0;
  font-size: 22rpx;
  line-height: 32rpx;
  color: #46514b;
  background: #fff;
  border: 1rpx solid #dfe6e2;
  border-radius: 14rpx;
}

.option::after,
.counter button::after,
.planner-submit::after {
  border: 0;
}

.option--stack {
  flex-direction: column;
}

.option--active {
  font-weight: 700;
  color: #fff;
  background: linear-gradient(145deg, #079c5c, #087d4d);
  border-color: #078e55;
  box-shadow: 0 8rpx 18rpx rgb(5 142 84 / 18%);
}

.option__sub {
  font-size: 16rpx;
  line-height: 24rpx;
  color: #99a49e;
}

.option--active .option__sub {
  color: rgb(255 255 255 / 76%);
}

.option__check {
  font-size: 18rpx;
}

.people-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14rpx;
}

.counter-card {
  padding: 19rpx;
  border: 1rpx solid #e2e9e5;
  border-radius: 16rpx;
}

.counter-card__label,
.counter-card__hint {
  display: block;
}

.counter-card__label {
  font-size: 23rpx;
  font-weight: 700;
}

.counter-card__hint {
  margin-top: 3rpx;
  font-size: 16rpx;
  color: #9aa49f;
}

.counter {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 16rpx;
}

.counter button {
  width: 52rpx;
  height: 52rpx;
  padding: 0;
  margin: 0;
  font-size: 28rpx;
  line-height: 50rpx;
  color: #087d4d;
  background: #eaf7f0;
  border: 0;
  border-radius: 12rpx;
}

.counter text {
  min-width: 36rpx;
  font-size: 27rpx;
  font-weight: 800;
  text-align: center;
}

.ages {
  padding: 18rpx;
  margin-top: 16rpx;
  background: #f4f9f6;
  border-radius: 14rpx;
}

.age-row {
  display: grid;
  grid-template-columns: 116rpx 1fr 66rpx;
  align-items: center;
  font-size: 18rpx;
}

.age-row__value {
  font-weight: 700;
  color: #07814d;
  text-align: right;
}

.textarea-wrap {
  position: relative;
}

.textarea {
  box-sizing: border-box;
  width: 100%;
  height: 180rpx;
  padding: 22rpx 24rpx 50rpx;
  font-size: 22rpx;
  line-height: 34rpx;
  color: #29342f;
  background: #fbfcfc;
  border: 1rpx solid #e0e6e3;
  border-radius: 16rpx;
}

.textarea__placeholder {
  color: #aeb7b2;
}

.textarea__count {
  position: absolute;
  right: 18rpx;
  bottom: 16rpx;
  font-size: 17rpx;
  color: #a1aaa5;
}

.planner-submit {
  display: flex;
  gap: 15rpx;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 94rpx;
  margin: 28rpx 0 0;
  font-size: 28rpx;
  font-weight: 800;
  line-height: 94rpx;
  color: #fff;
  background: linear-gradient(100deg, #079c5c, #067d4a);
  border: 0;
  border-radius: 20rpx;
  box-shadow: 0 13rpx 28rpx rgb(3 129 75 / 25%);
}

.planner-submit[disabled] {
  opacity: 0.68;
}

.planner-submit__spark {
  font-size: 31rpx;
}

.planner-footer {
  display: block;
  padding: 36rpx 20rpx 4rpx;
  font-size: 20rpx;
  line-height: 32rpx;
  color: #718078;
  text-align: center;
}
</style>
