<script setup lang="ts">
import type { CheckInBadge, CheckInBadgeDefinition, CheckInProfile, CheckInResult } from '@/domain/check-in'
import type { PoiResolved } from '@/domain/poi'
import PageState from '@/components/poi/PageState.vue'
import PoiImage from '@/components/poi/PoiImage.vue'
import { getCheckInTask, getCheckInTasks } from '@/data/check-in'
import { getPoiRepository } from '@/data/poi'
import {
  CHECK_IN_BADGES,
  CHECK_IN_POINTS,
  CheckInError,
} from '@/domain/check-in'
import { checkInAtPoi, getCheckInProfile } from '@/services/check-in'
import { getCurrentGcj02Location, isLocationPermissionDenied } from '@/services/location'

const pageState = ref<'loading' | 'ready' | 'invalid' | 'error'>('loading')
const poi = shallowRef<PoiResolved | null>(null)
const profile = ref<CheckInProfile>({ points: 0, badges: [], checkIns: [] })
const locating = ref(false)
const requestVersion = ref(0)
const routePoiId = ref('')
const privacyAuthorizationRequired = ref(false)

const checkedRecord = computed(() => poi.value
  ? profile.value.checkIns.find(record => record.poiId === poi.value?.id) ?? null
  : null)
const progressText = computed(() => {
  const availablePoiIds = new Set(getCheckInTasks().map(task => task.poiId))
  const completedCount = profile.value.checkIns.filter(record => availablePoiIds.has(record.poiId)).length
  return `${completedCount} / ${availablePoiIds.size}`
})
const checkInRadius = computed(() => poi.value ? getCheckInTask(poi.value.id)?.radiusMeters ?? 0 : 0)

function loadPage(poiId: string) {
  pageState.value = 'loading'
  try {
    const result = getPoiRepository().getPoiById(poiId)
    if (!result || !getCheckInTask(result.id)) {
      pageState.value = 'invalid'
      return
    }
    poi.value = result
    profile.value = getCheckInProfile()
    pageState.value = 'ready'
  }
  catch (error) {
    console.error('Failed to load check-in page', error)
    pageState.value = 'error'
  }
}

function returnToPoi() {
  uni.navigateBack({
    fail: () => uni.reLaunch({ url: '/pages/map/index' }),
  })
}

function openDataRecovery() {
  uni.navigateTo({ url: '/pages/check-in/records' })
}

function openRecords() {
  cancelPendingLocation()
  uni.navigateTo({ url: '/pages/check-in/records' })
}

function cancelPendingLocation() {
  if (!locating.value)
    return
  requestVersion.value += 1
  locating.value = false
}

function formatTime(value: string): string {
  const date = new Date(value)
  if (Number.isNaN(date.getTime()))
    return '时间待确认'
  const pad = (number: number) => String(number).padStart(2, '0')
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
}

function badgeDefinition(badge: CheckInBadge): CheckInBadgeDefinition | null {
  return CHECK_IN_BADGES.find(definition => definition.code === badge.code) ?? null
}

function checkInSuccessContent(result: CheckInResult): string {
  const badges = result.newBadges
    .map(badgeDefinition)
    .filter((badge): badge is CheckInBadgeDefinition => Boolean(badge))
  return badges.length > 0
    ? `已记录本次到访并获得 ${CHECK_IN_POINTS} 积分，同时解锁“${badges.map(badge => badge.name).join('、')}”。`
    : `已记录本次到访并获得 ${CHECK_IN_POINTS} 积分。`
}

function showLocationPermissionGuide() {
  uni.showModal({
    title: '需要位置权限',
    content: '打卡必须使用本次真实定位判断是否到达点位附近。授权仅在你点击打卡时触发。',
    confirmText: '去设置',
    success: result => result.confirm && uni.openSetting({}),
  })
}

function privacyAuthorizationSupported(): boolean {
  try {
    return typeof uni.requirePrivacyAuthorize === 'function'
      && (typeof uni.canIUse !== 'function' || uni.canIUse('requirePrivacyAuthorize'))
  }
  catch {
    return false
  }
}

function requirePrivacyAuthorization(): Promise<void> {
  if (!privacyAuthorizationSupported())
    return Promise.resolve()
  return new Promise((resolve, reject) => {
    uni.requirePrivacyAuthorize({
      success: () => resolve(),
      fail: error => reject(error),
    })
  })
}

function isPrivacyAuthorizationDenied(error: unknown): boolean {
  if (!error || typeof error !== 'object')
    return false
  const message = String((error as { errMsg?: unknown }).errMsg ?? '')
  return /privacy/i.test(message)
}

function acceptPrivacyAuthorization() {
  privacyAuthorizationRequired.value = false
  requestCheckIn()
}

async function requestCheckIn() {
  if (!poi.value || locating.value || checkedRecord.value)
    return

  const currentRequest = ++requestVersion.value
  locating.value = true
  try {
    await requirePrivacyAuthorization()
    if (currentRequest !== requestVersion.value)
      return
    const location = await getCurrentGcj02Location({ highAccuracyExpireTime: 10000, timeoutMs: 15000 })
    if (currentRequest !== requestVersion.value)
      return
    const result = checkInAtPoi(poi.value.id, location)
    profile.value = result.profile
    uni.showModal({
      title: '打卡成功',
      content: checkInSuccessContent(result),
      showCancel: false,
      confirmText: '查看记录',
      success: modalResult => modalResult.confirm && openRecords(),
    })
  }
  catch (error) {
    if (currentRequest !== requestVersion.value)
      return
    if (isPrivacyAuthorizationDenied(error)) {
      privacyAuthorizationRequired.value = true
      return
    }
    if (isLocationPermissionDenied(error)) {
      showLocationPermissionGuide()
      return
    }
    const message = error instanceof Error ? error.message : '暂时无法完成打卡，请稍后重试'
    uni.showModal({
      title: error instanceof CheckInError && error.code === 'not_eligible' ? '尚不能打卡' : '打卡未完成',
      content: message,
      showCancel: false,
    })
  }
  finally {
    if (currentRequest === requestVersion.value)
      locating.value = false
  }
}

onLoad((query) => {
  const rawPoiId = typeof query?.poiId === 'string' ? query.poiId : ''
  try {
    routePoiId.value = decodeURIComponent(rawPoiId)
    loadPage(routePoiId.value)
  }
  catch {
    pageState.value = 'invalid'
  }
})

onShow(() => {
  if (pageState.value === 'error' && routePoiId.value) {
    loadPage(routePoiId.value)
    return
  }
  try {
    profile.value = getCheckInProfile()
    if (poi.value && getCheckInTask(poi.value.id))
      pageState.value = 'ready'
  }
  catch (error) {
    console.error('Failed to refresh check-in profile', error)
    pageState.value = 'error'
  }
})

onUnload(() => {
  cancelPendingLocation()
})

onHide(cancelPendingLocation)
</script>

<template>
  <view class="check-in-page">
    <PageState v-if="pageState === 'loading'" state="loading" title="正在准备打卡…" />
    <PageState
      v-else-if="pageState === 'invalid'"
      state="empty"
      title="该点位暂不可打卡"
      description="点位可能已更新，请返回地图重新选择"
      action-label="返回地图"
      @action="returnToPoi"
    />
    <PageState
      v-else-if="pageState === 'error'"
      state="error"
      title="打卡数据暂不可用"
      description="请前往“我的打卡”检查并按提示处理本机数据"
      action-label="去处理"
      @action="openDataRecovery"
    />

    <template v-else-if="poi">
      <view class="check-in-hero">
        <text class="check-in-hero__eyebrow">ON-SITE CHECK-IN</text>
        <text class="check-in-hero__title">抵达光明，留下足迹</text>
        <text class="check-in-hero__description">每次打卡都使用本次真实定位核验；位置只用于当场判断，不写入本机记录。</text>
        <button class="check-in-summary" @click="openRecords">
          <view>
            <text class="check-in-summary__label">本机积分</text>
            <text class="check-in-summary__value">{{ profile.points }}</text>
          </view>
          <view>
            <text class="check-in-summary__label">已打卡</text>
            <text class="check-in-summary__value">{{ progressText }}</text>
          </view>
          <view>
            <text class="check-in-summary__label">徽章</text>
            <text class="check-in-summary__value">{{ profile.badges.length }}</text>
          </view>
          <text class="check-in-summary__arrow">›</text>
        </button>
      </view>

      <view class="check-in-content">
        <view class="poi-check-card">
          <PoiImage
            :src="poi.coverImage.url"
            :alt="poi.coverImage.alt"
            width="152rpx"
            height="152rpx"
            radius="18rpx"
          />
          <view class="poi-check-card__copy">
            <text class="poi-check-card__category">{{ poi.category.name }}</text>
            <text class="poi-check-card__name">{{ poi.name }}</text>
            <text class="poi-check-card__address">{{ poi.address }}</text>
          </view>
        </view>

        <view v-if="checkedRecord" class="check-in-status check-in-status--done">
          <view class="check-in-status__mark">✓</view>
          <view class="check-in-status__copy">
            <text class="check-in-status__title">该点位已完成打卡</text>
            <text>{{ formatTime(checkedRecord.checkedInAt) }} · 获得 {{ checkedRecord.pointsAwarded }} 积分</text>
          </view>
        </view>
        <view v-else class="check-in-status">
          <view class="check-in-status__mark">◎</view>
          <view class="check-in-status__copy">
            <text class="check-in-status__title">请在点位附近发起打卡</text>
            <text>需在 {{ checkInRadius }} 米范围内，且定位精度满足要求</text>
          </view>
        </view>

        <button
          class="check-in-action"
          :class="{ 'check-in-action--done': checkedRecord }"
          :disabled="locating || Boolean(checkedRecord)"
          @click="requestCheckIn"
        >
          {{ checkedRecord ? '已完成打卡' : locating ? '正在获取本次位置…' : `验证位置并打卡 +${CHECK_IN_POINTS}` }}
        </button>

        <view v-if="privacyAuthorizationRequired" class="check-in-privacy-consent">
          <text class="check-in-privacy-consent__title">打卡需要位置隐私授权</text>
          <text>同意后才会请求本次位置；位置仅用于到点判断，不保存精确经纬度。</text>
          <button
            id="check-in-privacy-agree"
            open-type="agreePrivacyAuthorization"
            @agreeprivacyauthorization="acceptPrivacyAuthorization"
          >
            同意并继续打卡
          </button>
        </view>

        <view class="check-in-rules">
          <text class="check-in-rules__title">打卡规则</text>
          <view><text>01</text><text>每个开放打卡点位在本机仅可打卡一次</text></view>
          <view><text>02</text><text>每次打卡重新请求 GCJ-02 位置，不复用历史定位</text></view>
          <view><text>03</text><text>成功后增加积分，并按累计点位数解锁徽章</text></view>
          <view><text>04</text><text>系统级虚拟定位无法由本 POC 完全识别，正式版需增加风控</text></view>
        </view>
      </view>
    </template>
  </view>
</template>

<route lang="yaml">
layout: map
style:
  navigationBarTitleText: 点位打卡
  navigationBarBackgroundColor: '#ffffff'
  navigationBarTextStyle: black
  backgroundColor: '#f4f8f6'
</route>

<style scoped lang="scss">
.check-in-page {
  min-height: 100vh;
  color: #18201d;
  background: #f4f8f6;
}

.check-in-hero {
  padding: 46rpx 32rpx 34rpx;
  color: #fff;
  background: linear-gradient(145deg, #123d35, #08724d 62%, #07573d);
}

.check-in-hero__eyebrow {
  display: block;
  font-size: 18rpx;
  line-height: 28rpx;
  color: #9fe0bd;
  letter-spacing: 4rpx;
}

.check-in-hero__title {
  display: block;
  margin-top: 10rpx;
  font-size: 42rpx;
  font-weight: 800;
  line-height: 58rpx;
}

.check-in-hero__description {
  display: block;
  margin-top: 14rpx;
  font-size: 23rpx;
  line-height: 38rpx;
  color: rgb(239 255 246 / 78%);
}

.check-in-summary {
  display: flex;
  gap: 20rpx;
  align-items: center;
  width: 100%;
  padding: 22rpx 24rpx;
  margin: 30rpx 0 0;
  color: #fff;
  text-align: left;
  background: rgb(255 255 255 / 11%);
  border: 1rpx solid rgb(255 255 255 / 22%);
  border-radius: 20rpx;
}

.check-in-summary::after,
.check-in-action::after {
  border: 0;
}

.check-in-summary view {
  display: flex;
  flex: 1;
  flex-direction: column;
}

.check-in-summary__label {
  font-size: 18rpx;
  color: rgb(239 255 246 / 68%);
}

.check-in-summary__value {
  margin-top: 5rpx;
  font-size: 29rpx;
  font-weight: 800;
  line-height: 40rpx;
}

.check-in-summary__arrow {
  font-size: 40rpx;
  color: #9fe0bd;
}

.check-in-content {
  padding: 28rpx 28rpx calc(48rpx + env(safe-area-inset-bottom));
}

.poi-check-card,
.check-in-status,
.check-in-rules,
.check-in-privacy-consent {
  background: #fff;
  border: 1rpx solid #dce6e1;
  border-radius: 22rpx;
  box-shadow: 0 10rpx 28rpx rgb(20 78 53 / 5%);
}

.check-in-privacy-consent {
  display: flex;
  flex-direction: column;
  padding: 24rpx;
  margin-top: 20rpx;
  font-size: 21rpx;
  line-height: 34rpx;
  color: #5e6b65;
}

.check-in-privacy-consent__title {
  margin-bottom: 6rpx;
  font-size: 26rpx;
  font-weight: 800;
  color: #18201d;
}

.check-in-privacy-consent button {
  width: 100%;
  height: 70rpx;
  margin: 18rpx 0 0;
  font-size: 23rpx;
  line-height: 70rpx;
  color: #fff;
  background: #167a5b;
  border: 0;
  border-radius: 14rpx;
}

.check-in-privacy-consent button::after {
  border: 0;
}

.poi-check-card {
  display: flex;
  gap: 22rpx;
  align-items: center;
  padding: 24rpx;
}

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

.poi-check-card__category {
  font-size: 20rpx;
  color: #16805c;
}

.poi-check-card__name {
  margin-top: 5rpx;
  font-size: 30rpx;
  font-weight: 800;
  line-height: 42rpx;
}

.poi-check-card__address {
  display: -webkit-box;
  margin-top: 7rpx;
  overflow: hidden;
  font-size: 21rpx;
  line-height: 32rpx;
  color: #718078;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.check-in-status {
  display: flex;
  gap: 18rpx;
  align-items: center;
  padding: 24rpx;
  margin-top: 20rpx;
  color: #5e6b65;
}

.check-in-status--done {
  color: #315d4a;
  background: #e8f4ee;
  border-color: #cfe4d9;
}

.check-in-status__mark {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 64rpx;
  height: 64rpx;
  font-size: 30rpx;
  font-weight: 800;
  color: #fff;
  background: #167a5b;
  border-radius: 50%;
}

.check-in-status__copy {
  display: flex;
  flex-direction: column;
  font-size: 21rpx;
  line-height: 32rpx;
}

.check-in-status__title {
  margin-bottom: 4rpx;
  font-size: 26rpx;
  font-weight: 750;
  line-height: 38rpx;
  color: #18201d;
}

.check-in-action {
  width: 100%;
  height: 92rpx;
  margin: 24rpx 0 0;
  font-size: 27rpx;
  font-weight: 750;
  line-height: 92rpx;
  color: #fff;
  background: #167a5b;
  border: 0;
  border-radius: 20rpx;
  box-shadow: 0 14rpx 30rpx rgb(14 98 71 / 16%);
}

.check-in-action--done,
.check-in-action[disabled] {
  color: #718078;
  background: #e3e9e6;
  box-shadow: none;
}

.check-in-rules {
  padding: 26rpx;
  margin-top: 28rpx;
}

.check-in-rules__title {
  display: block;
  margin-bottom: 12rpx;
  font-size: 28rpx;
  font-weight: 800;
  line-height: 40rpx;
}

.check-in-rules view {
  display: flex;
  gap: 16rpx;
  padding: 12rpx 0;
  font-size: 22rpx;
  line-height: 34rpx;
  color: #5e6b65;
  border-bottom: 1rpx solid #edf1ef;
}

.check-in-rules view:last-child {
  border-bottom: 0;
}

.check-in-rules view text:first-child {
  flex: none;
  font-family: Georgia, serif;
  color: #16805c;
}
</style>
