<template>
  <view class="page-shell report-page">
    <view class="report-nav nav-gradient" :style="navSafeStyle">
      <view class="nav-back" @click="goBack"><AppIcon name="back" :size="18" color="#ffffff" /></view>
      <text class="nav-title">填写报告</text>
      <view style="width:32px"></view>
    </view>

    <view class="report-body">
      <!-- 基础信息 -->
      <view class="section">
        <view class="section-head section-head--clickable" @click="basicInfoCollapsed = !basicInfoCollapsed">
          <text class="section-label">基础信息</text>
          <text class="section-toggle">{{ basicInfoCollapsed ? '展开' : '收起' }}</text>
        </view>
        <view v-show="!basicInfoCollapsed">
          <view class="form-row">
            <text class="app-form-label">宠物名字</text>
            <input v-model="report.petName" class="app-form-input" placeholder="请输入宠物名字" />
          </view>
          <view class="form-row">
            <text class="app-form-label">服务类型</text>
            <picker mode="selector" :range="serviceColumns" range-key="label" @change="onServiceConfirm">
              <view class="app-form-input app-form-picker">
                <text :class="{ 'app-form-placeholder': !report.serviceType }">{{ report.serviceType || '请选择' }}</text>
                <text class="app-form-arrow">›</text>
              </view>
            </picker>
          </view>
          <view class="form-row">
            <text class="app-form-label">服务时间</text>
            <view class="time-row">
              <picker mode="date" :value="serviceDate" @change="onServiceDateChange" class="time-picker">
                <view class="app-form-input app-form-picker">
                  <text :class="{ 'app-form-placeholder': !serviceDate }">{{ serviceDate || '日期' }}</text>
                </view>
              </picker>
              <picker
                mode="selector"
                :range="HALF_HOUR_TIME_SLOTS"
                :value="halfHourServiceTimeIndex"
                @change="onServiceTimeChange"
                class="time-picker"
              >
                <view class="app-form-input app-form-picker">
                  <text :class="{ 'app-form-placeholder': !serviceTimePart }">{{ serviceTimePart || '时间' }}</text>
                </view>
              </picker>
            </view>
          </view>
        </view>
      </view>

      <!-- 服务照片 — 核心区域 -->
      <view class="section">
        <view class="section-head">
          <text class="section-label">服务前后对比</text>
          <text class="section-hint">上传照片让宠主看到变化</text>
        </view>

        <view class="photo-compare">
          <view class="photo-side">
            <view class="photo-side-label">
              <view class="dot dot-before"></view>
              <text>服务前</text>
            </view>
            <view class="photo-grid">
              <view v-for="(item, idx) in report.beforeMedia" :key="'b'+idx" class="photo-thumb">
                <image :src="item.url" class="thumb-img" mode="aspectFill" />
                <view v-if="item.uploading" class="media-mask">
                  <text class="media-mask-text">上传中…</text>
                </view>
                <view v-else-if="item.failed" class="media-mask media-mask--error" @click="retryUpload('beforeMedia', idx)">
                  <text class="media-mask-text">上传失败，点此重试</text>
                </view>
                <view class="thumb-del" @click.stop="removeMedia('beforeMedia', idx)">✕</view>
              </view>
              <view v-if="report.beforeMedia.length < 4" class="photo-add" @click="uploadPhoto('beforeMedia')">
                <AppIcon name="camera" :size="20" color="#bbbbb5" />
              </view>
            </view>
          </view>

          <view class="photo-divider"></view>

          <view class="photo-side">
            <view class="photo-side-label">
              <view class="dot dot-after"></view>
              <text>服务后</text>
            </view>
            <view class="photo-grid">
              <view v-for="(item, idx) in report.afterMedia" :key="'a'+idx" class="photo-thumb">
                <image :src="item.url" class="thumb-img" mode="aspectFill" />
                <view v-if="item.uploading" class="media-mask">
                  <text class="media-mask-text">上传中…</text>
                </view>
                <view v-else-if="item.failed" class="media-mask media-mask--error" @click="retryUpload('afterMedia', idx)">
                  <text class="media-mask-text">上传失败，点此重试</text>
                </view>
                <view class="thumb-del" @click.stop="removeMedia('afterMedia', idx)">✕</view>
              </view>
              <view v-if="report.afterMedia.length < 4" class="photo-add" @click="uploadPhoto('afterMedia')">
                <AppIcon name="camera" :size="20" color="#bbbbb5" />
              </view>
            </view>
          </view>
        </view>
      </view>

      <!-- 服务过程中 -->
      <view class="section">
        <view class="section-head">
          <text class="section-label">服务过程中</text>
          <text class="section-hint">照片或视频，记录服务细节</text>
        </view>

        <view class="during-add-row">
          <view class="during-add-btn" @click="uploadDuring()">
            <AppIcon name="camera" :size="18" color="#bbbbb5" />
            <text>添加照片或视频</text>
          </view>
        </view>

        <view v-if="report.duringMedia.length > 0" class="during-grid">
          <view v-for="(item, idx) in report.duringMedia" :key="'d'+idx" class="photo-thumb">
            <image v-if="item.mediaType === 'photo'" :src="item.url" class="thumb-img" mode="aspectFill" />
            <video
              v-else
              :src="item.url"
              class="thumb-img"
              :show-center-play-btn="false"
              :show-play-btn="false"
              objectFit="cover"
            />
            <view v-if="item.mediaType === 'video'" class="video-badge">
              <text>视频</text>
            </view>
            <view v-if="item.uploading" class="media-mask">
              <text class="media-mask-text">上传中…</text>
            </view>
            <view v-else-if="item.failed" class="media-mask media-mask--error" @click="retryUpload('duringMedia', idx)">
              <text class="media-mask-text">上传失败，点此重试</text>
            </view>
            <view class="thumb-del" @click.stop="removeMedia('duringMedia', idx)">✕</view>
          </view>
        </view>
      </view>

      <!-- 备注 -->
      <view class="section">
        <view class="section-head">
          <text class="section-label">备注</text>
        </view>
        <textarea
          v-model="report.remark"
          class="app-form-textarea"
          placeholder="服务细节、注意事项（可选）"
          @focus="onRemarkFocus"
          @blur="onRemarkBlur"
        />
      </view>

    </view>

    <view
      class="submit-fixed"
      :class="{ 'submit-fixed--scrolled': hasScrolled }"
      :style="submitFixedStyle"
    >
      <view class="report-lock-hint">
        <text>提交后不可修改，请确认照片与备注无误。</text>
      </view>
      <button class="app-btn-main app-btn-primary submit-btn--fixed" :disabled="submitting" @click="submitReport">
        {{ submitting ? '提交中…' : '生成报告' }}
      </button>
    </view>

    <view v-if="submitting" class="loading-mask">
      <view class="loading-box">
        <view class="loading-spinner"></view>
        <text>正在生成…</text>
      </view>
    </view>

    <ReportShareModal
      :show="shareModalOpen"
      :report-token="shareToken"
      :pet-name="sharePetName"
      :store-name="shareStoreName"
      :service-type="shareServiceType"
      @close="onShareModalClose"
      @preview="onSharePreview"
    />
  </view>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { onPageScroll } from '@dcloudio/uni-app'
import { createReport, getServiceTypeList, imgUrl, BASE_URL } from '../../api/index.js'
import AppIcon from '../../components/AppIcon.vue'
import ReportShareModal from '../../components/report/ReportShareModal.vue'
import { getUserSession, getStoreSession } from '../../utils/session.js'
import { useNavigator } from '../../composables/useNavigator.js'
import {
  HALF_HOUR_TIME_SLOTS,
  DEFAULT_HALF_HOUR_TIME,
  normalizeToHalfHour,
  halfHourPickerIndex,
  suggestHalfHourForYMD
} from '../../utils/halfHourTime.js'

const userInfo = getUserSession()
const storeInfo = getStoreSession()
const { navigateTo } = useNavigator()
const navSafeStyle = (() => {
  const statusBarHeight = uni.getSystemInfoSync?.().statusBarHeight || 20
  let navHeight = statusBarHeight + 44
  const menuRect = uni.getMenuButtonBoundingClientRect?.()
  if (menuRect && menuRect.top && menuRect.height) {
    const verticalGap = Math.max(menuRect.top - statusBarHeight, 4)
    navHeight = statusBarHeight + verticalGap * 2 + menuRect.height
  }
  return `padding-top:${statusBarHeight}px;height:${navHeight}px;`
})()

// beforeMedia / afterMedia: [{url, mediaType}] mediaType 固定为 "photo"
// duringMedia: [{url, mediaType}] mediaType 可以是 "photo" 或 "video"
const report = ref({
  petName: '',
  serviceType: '',
  appointmentTime: '',
  beforeMedia: [],
  afterMedia: [],
  duringMedia: [],
  remark: ''
})
const serviceTypes = ref([])
const currentAppointmentId = ref(null)
const submitting = ref(false)
const shareModalOpen = ref(false)
const shareToken = ref('')
const sharePetName = ref('')
const shareStoreName = ref('')
const shareServiceType = ref('')
const basicInfoCollapsed = ref(false)
const hasScrolled = ref(false)
const keyboardHeight = ref(0)
const remarkFocused = ref(false)
const autoCollapsedByKeyboard = ref(false)
let keyboardChangeHandler = null

const submitFixedStyle = computed(() => {
  const bottom = keyboardHeight.value > 0 ? `${keyboardHeight.value}px` : '0px'
  return `bottom:${bottom};`
})

const allMediaItems = computed(() => ([
  ...report.value.beforeMedia,
  ...report.value.afterMedia,
  ...report.value.duringMedia
]))

const hasUploadingMedia = computed(() => allMediaItems.value.some((i) => !!i.uploading))
const hasFailedMedia = computed(() => allMediaItems.value.some((i) => !!i.failed))

const serviceColumns = computed(() =>
  serviceTypes.value.map(s => ({ label: s.name, value: s.name }))
)

const loadServiceTypes = async () => {
  if (!storeInfo.id) return
  const res = await getServiceTypeList(storeInfo.id)
  if (res.code === 200) serviceTypes.value = res.data
}

/**
 * 上传照片（用于服务前/后）
 */
const friendlyUploadError = (rawMsg) => {
  const msg = (rawMsg || '').toString()
  if (!msg) return '上传失败'
  if (msg.includes('request:fail')) return '网络异常，请重试'
  if (msg.includes('timeout')) return '上传超时，请重试'
  if (msg.includes('只能上传图片或视频')) return '格式不支持'
  if (msg.includes('413') || msg.includes('Too Large') || msg.includes('HTTP 413')) {
    return '文件过大：可先压缩视频；仍失败请把网关上传上限调到≥200MB'
  }
  if (msg.includes('HTTP 502') || msg.includes('HTTP 503') || msg.includes('HTTP 504')) return '服务暂不可用'
  if (/HTTP\s(4|5)\d{2}/.test(msg)) return '上传失败，请重试'
  return msg
}

/** 大视频上传较慢，单独加长超时（毫秒） */
const UPLOAD_TIMEOUT_MS = 300000

/** 与后端 max-file-size 对齐，前端提前提示，避免长时间上传后 413 */
const MAX_VIDEO_MB = 190

const buildPendingItem = (localPath, mediaType) => ({
  url: localPath,
  localPath,
  mediaType,
  uploading: true,
  failed: false,
  error: ''
})

const setUploadSuccess = (item, uploadedUrl) => {
  try {
    item.url = imgUrl(uploadedUrl)
  } catch (_) {
    item.url = uploadedUrl || ''
  }
  item.uploading = false
  item.failed = false
  item.error = ''
}

const setUploadFail = (item, reason) => {
  item.uploading = false
  item.failed = true
  item.error = reason || '上传失败'
}

/**
 * 微信小程序：异步上传回调里改嵌套字段后，界面偶发不刷新，点别处才更新。
 * nextTick + 浅拷贝换数组引用，强制重绘缩略图上的遮罩。
 */
async function pokeMediaList(field) {
  if (!field || !Array.isArray(report.value[field])) return
  await nextTick()
  report.value[field] = report.value[field].map((row) => ({ ...row }))
  await nextTick()
  await new Promise((r) => setTimeout(r, 16))
}

/** 微信端 uploadFile：statusCode 可能为字符串或缺失；须按 2xx 判断 */
function uploadHttpOk(sc) {
  if (sc == null || sc === '') return true
  const n = Number(sc)
  if (!Number.isNaN(n)) return n >= 200 && n < 300
  return String(sc) === '200'
}

function parseUploadJson(raw) {
  if (raw == null || raw === '') return null
  if (typeof raw === 'object') {
    if (Array.isArray(raw)) return null
    if (typeof ArrayBuffer !== 'undefined' && raw instanceof ArrayBuffer) {
      const s = new TextDecoder('utf-8', { fatal: false }).decode(raw)
      return parseUploadJson(s)
    }
    if (typeof Uint8Array !== 'undefined' && raw instanceof Uint8Array) {
      const s = new TextDecoder('utf-8', { fatal: false }).decode(raw)
      return parseUploadJson(s)
    }
    return raw
  }
  const s = String(raw).replace(/^\uFEFF/, '').trim()
  if (!s) return null
  try {
    return JSON.parse(s)
  } catch {
    return null
  }
}

/** 编码异常或解析失败时，用正则从纯 ASCII 字段恢复 url（不依赖 message） */
function parseUploadFallback(raw) {
  const s =
    typeof raw === 'string'
      ? raw
      : typeof raw === 'object' && raw !== null && typeof raw.toString === 'function'
        ? raw.toString()
        : ''
  const codeM = s.match(/"code"\s*:\s*(\d+)/)
  const urlM = s.match(/"url"\s*:\s*"(\/api\/upload\/image\/[^"]*)"/)
  if (codeM && Number(codeM[1]) === 200 && urlM) {
    return { code: 200, data: { url: urlM[1] } }
  }
  return null
}

function extractUploadedUrl(data) {
  if (!data || typeof data !== 'object') return ''
  const inner = data.data
  if (inner && typeof inner === 'object' && inner.url) return String(inner.url)
  if (typeof inner === 'string' && inner.startsWith('/')) return inner
  if (data.url) return String(data.url)
  return ''
}

function isBizSuccess(data) {
  const c = data?.code
  return c === 200 || c === '200'
}

const uploadSingleFile = (filePath) =>
  new Promise((resolve) => {
    let settled = false
    const done = (r) => {
      if (settled) return
      settled = true
      resolve(r)
    }
    const timer = setTimeout(() => {
      done({ ok: false, message: '上传超时，请重试' })
    }, UPLOAD_TIMEOUT_MS)

    uni.uploadFile({
      url: `${BASE_URL}/upload/image`,
      filePath,
      name: 'file',
      timeout: UPLOAD_TIMEOUT_MS,
      success: (uploadRes) => {
        try {
          clearTimeout(timer)
          if (!uploadHttpOk(uploadRes.statusCode)) {
            done({ ok: false, message: friendlyUploadError(`HTTP ${uploadRes.statusCode}`) })
            return
          }
          let data = parseUploadJson(uploadRes.data)
          let serverUrl = extractUploadedUrl(data)
          if (!data || !isBizSuccess(data) || !serverUrl) {
            const fb = parseUploadFallback(uploadRes.data)
            if (fb) {
              data = fb
              serverUrl = extractUploadedUrl(data)
            }
          }
          if (isBizSuccess(data) && serverUrl) {
            done({ ok: true, url: serverUrl })
            return
          }
          const msg = data?.message || data?.msg || ''
          done({
            ok: false,
            message: msg ? friendlyUploadError(msg) : '上传返回异常，请重试'
          })
        } catch (e) {
          done({ ok: false, message: '处理上传结果失败，请重试' })
        }
      },
      fail: (err) => {
        clearTimeout(timer)
        done({ ok: false, message: friendlyUploadError(err?.errMsg) })
      }
    })
  })

const showBatchUploadFail = (count, firstReason) => {
  uni.showToast({
    title: `${count}个上传失败：${firstReason || '请重试'}`.slice(0, 36),
    icon: 'none',
    duration: 2800
  })
}

const retryUpload = async (field, idx) => {
  const item = report.value[field]?.[idx]
  if (!item || item.uploading) return
  const filePath = item.localPath || item.url
  if (!filePath) {
    uni.showToast({ title: '缺少本地文件，无法重试', icon: 'none' })
    return
  }
  item.uploading = true
  item.failed = false
  item.error = ''
  uni.showLoading({ title: '重试上传...' })
  const r = await uploadSingleFile(filePath)
  uni.hideLoading()
  if (r.ok) {
    setUploadSuccess(item, r.url)
    uni.showToast({ title: '重试成功', icon: 'success' })
  } else {
    setUploadFail(item, r.message)
    uni.showToast({ title: `重试失败：${r.message}`.slice(0, 36), icon: 'none' })
  }
  await pokeMediaList(field)
}

const uploadPhoto = (field) => {
  const fieldMax = 4
  const maxTotal = 12
  if (report.value[field].length >= maxTotal) {
    return uni.showToast({ title: `最多上传${maxTotal}个`, icon: 'none' })
  }
  const count = Math.min(fieldMax - report.value[field].length, maxTotal - report.value[field].length, 4)
  uni.chooseImage({
    count,
    success: async (res) => {
      const tempPaths = res.tempFilePaths
      if (!tempPaths?.length) return
      uni.showLoading({ title: '上传中...' })
      let failCount = 0
      let firstReason = ''
      for (const tempPath of tempPaths) {
        const item = buildPendingItem(tempPath, 'photo')
        report.value[field].push(item)
        const r = await uploadSingleFile(tempPath)
        if (r.ok) {
          setUploadSuccess(item, r.url)
        } else {
          failCount++
          if (!firstReason) firstReason = r.message
          setUploadFail(item, r.message)
        }
        await pokeMediaList(field)
      }
      uni.hideLoading()
      if (failCount > 0) showBatchUploadFail(failCount, firstReason)
    },
    fail: (err) => {
      const msg = err?.errMsg || ''
      if (msg.includes('cancel')) return
      uni.showToast({ title: friendlyUploadError(msg), icon: 'none' })
    },
  })
}

/**
 * 上传视频（用于过程中）
 */
const uploadVideo = (field) => {
  const maxTotal = 12
  if (report.value[field].length >= maxTotal) {
    return uni.showToast({ title: `最多上传${maxTotal}个`, icon: 'none' })
  }
  uni.chooseMedia({
    count: 1,
    mediaType: ['video'],
    success: async (res) => {
      const tempFiles = res.tempFiles
      if (!tempFiles?.length) return
      uni.showLoading({ title: '上传中...' })
      let failCount = 0
      let firstReason = ''
      for (const f of tempFiles) {
        const item = buildPendingItem(f.tempFilePath, 'video')
        report.value[field].push(item)
        const sizeMB = (f.size || 0) / 1024 / 1024
        if (sizeMB > MAX_VIDEO_MB) {
          failCount++
          if (!firstReason) firstReason = `视频请不超过${MAX_VIDEO_MB}MB`
          setUploadFail(item, `视频请不超过${MAX_VIDEO_MB}MB`)
          await pokeMediaList(field)
          continue
        }
        const r = await uploadSingleFile(f.tempFilePath)
        if (r.ok) {
          setUploadSuccess(item, r.url)
        } else {
          failCount++
          if (!firstReason) firstReason = r.message
          setUploadFail(item, r.message)
        }
        await pokeMediaList(field)
      }
      uni.hideLoading()
      if (failCount > 0) showBatchUploadFail(failCount, firstReason)
    },
    fail: (err) => {
      const msg = err?.errMsg || ''
      if (msg.includes('cancel')) return
      uni.showToast({ title: friendlyUploadError(msg), icon: 'none' })
    },
  })
}

/**
 * 上传过程中媒体（一次选择，支持照片和视频混选）
 */
const uploadDuring = () => {
  const maxTotal = 12
  if (report.value.duringMedia.length >= maxTotal) {
    return uni.showToast({ title: `最多上传${maxTotal}个`, icon: 'none' })
  }
  const remain = maxTotal - report.value.duringMedia.length
  uni.chooseMedia({
    count: Math.min(remain, 9),
    mediaType: ['image', 'video'],
    success: async (res) => {
      const tempFiles = res.tempFiles || []
      if (!tempFiles.length) return
      uni.showLoading({ title: '上传中...' })
      let failCount = 0
      let firstReason = ''
      for (const f of tempFiles) {
        const path = f.tempFilePath || ''
        const lowerPath = path.toLowerCase()
        const isVideo =
          f.fileType === 'video' ||
          lowerPath.endsWith('.mp4') ||
          lowerPath.endsWith('.mov') ||
          lowerPath.endsWith('.m4v') ||
          lowerPath.endsWith('.webm') ||
          lowerPath.endsWith('.avi')
        const mediaType = isVideo ? 'video' : 'photo'
        const item = buildPendingItem(path, mediaType)
        report.value.duringMedia.push(item)

        if (isVideo) {
          const sizeMB = (f.size || 0) / 1024 / 1024
          if (sizeMB > MAX_VIDEO_MB) {
            failCount++
            if (!firstReason) firstReason = `视频请不超过${MAX_VIDEO_MB}MB`
            setUploadFail(item, `视频请不超过${MAX_VIDEO_MB}MB`)
            await pokeMediaList('duringMedia')
            continue
          }
        }

        const r = await uploadSingleFile(path)
        if (r.ok) {
          setUploadSuccess(item, r.url)
        } else {
          failCount++
          if (!firstReason) firstReason = r.message
          setUploadFail(item, r.message)
        }
        await pokeMediaList('duringMedia')
      }
      uni.hideLoading()
      if (failCount > 0) showBatchUploadFail(failCount, firstReason)
    },
    fail: (err) => {
      const msg = err?.errMsg || ''
      if (msg.includes('cancel')) return
      uni.showToast({ title: friendlyUploadError(msg), icon: 'none' })
    },
  })
}

const removeMedia = (field, idx) => { report.value[field].splice(idx, 1) }

const onServiceConfirm = (e) => {
  report.value.serviceType = serviceColumns.value[e.detail.value].value
}

const serviceDate = computed(() => {
  const raw = report.value.appointmentTime || ''
  return raw.includes('T') ? raw.split('T')[0] : ''
})
const serviceTimePart = computed(() => {
  const raw = report.value.appointmentTime || ''
  if (!raw.includes('T')) return ''
  const part = (raw.split('T')[1] || '').slice(0, 5)
  return normalizeToHalfHour(part)
})

const halfHourServiceTimeIndex = computed(() => halfHourPickerIndex(serviceTimePart.value))

const onServiceDateChange = (e) => {
  const date = e?.detail?.value || ''
  if (!date) {
    report.value.appointmentTime = ''
    return
  }
  const time = normalizeToHalfHour(suggestHalfHourForYMD(date))
  report.value.appointmentTime = `${date}T${time}`
}

const onServiceTimeChange = (e) => {
  const idx = Number(e.detail.value)
  const time = HALF_HOUR_TIME_SLOTS[idx]
  const date = serviceDate.value
  if (!date) { uni.showToast({ title: '请先选择日期', icon: 'none' }); return }
  report.value.appointmentTime = `${date}T${time}`
}

const submitReport = async () => {
  if (hasUploadingMedia.value) return uni.showToast({ title: '还有素材上传中，请稍候', icon: 'none' })
  if (hasFailedMedia.value) {
    return uni.showToast({ title: '请先点缩略图右上角✕删除失败项', icon: 'none', duration: 2800 })
  }
  if (!report.value.petName) return uni.showToast({ title: '请输入宠物名字', icon: 'none' })
  if (!report.value.serviceType) return uni.showToast({ title: '请选择服务类型', icon: 'none' })
  if (!report.value.appointmentTime) return uni.showToast({ title: '请选择服务时间', icon: 'none' })
  submitting.value = true

  // 构建图片列表
  const images = []
  report.value.beforeMedia.filter((item) => !item.uploading && !item.failed).forEach((item, i) => {
    images.push({ photoUrl: item.url, photoType: 'before', mediaType: item.mediaType || 'photo', sortOrder: i })
  })
  report.value.afterMedia.filter((item) => !item.uploading && !item.failed).forEach((item, i) => {
    images.push({ photoUrl: item.url, photoType: 'after', mediaType: item.mediaType || 'photo', sortOrder: i })
  })
  report.value.duringMedia.filter((item) => !item.uploading && !item.failed).forEach((item, i) => {
    images.push({ photoUrl: item.url, photoType: 'during', mediaType: item.mediaType || 'photo', sortOrder: i })
  })

  const payload = {
    appointmentId: currentAppointmentId.value || null,
    userId: userInfo.id,
    petName: report.value.petName,
    serviceType: report.value.serviceType,
    appointmentTime: report.value.appointmentTime || null,
    remark: report.value.remark,
    images
  }

  const res = await createReport(payload)
  submitting.value = false
  if (res.code === 200) {
    const token = res.data.reportToken
    shareToken.value = token
    sharePetName.value = report.value.petName || ''
    shareStoreName.value = storeInfo?.name || ''
    shareServiceType.value = report.value.serviceType || ''
    report.value = { petName: '', serviceType: '', appointmentTime: '', beforeMedia: [], afterMedia: [], duringMedia: [], remark: '' }
    currentAppointmentId.value = null
    basicInfoCollapsed.value = false
    uni.showToast({ title: '已生成报告', icon: 'success' })
    shareModalOpen.value = true
  } else {
    uni.showToast({ title: res.message || '提交失败', icon: 'none' })
  }
}

const onShareModalClose = () => {
  shareModalOpen.value = false
  shareToken.value = ''
  sharePetName.value = ''
  shareStoreName.value = ''
  shareServiceType.value = ''
}

const onSharePreview = () => {
  const t = shareToken.value
  shareModalOpen.value = false
  shareToken.value = ''
  sharePetName.value = ''
  shareStoreName.value = ''
  shareServiceType.value = ''
  if (t) {
    uni.navigateTo({ url: `/pages/report-view/reportView?token=${encodeURIComponent(t)}` })
  }
}

const goBack = () => {
  const pages = getCurrentPages()
  if (pages.length > 1) {
    uni.navigateBack()
    return
  }
  navigateTo('report')
}

onMounted(async () => {
  if (typeof uni.onKeyboardHeightChange === 'function') {
    keyboardChangeHandler = (res) => {
      const h = Math.max(0, Number(res?.height || 0))
      keyboardHeight.value = h

      // 仅在备注输入态自动折叠基础信息，减少输入时视觉干扰。
      if (h > 0 && remarkFocused.value && !basicInfoCollapsed.value) {
        basicInfoCollapsed.value = true
        autoCollapsedByKeyboard.value = true
      }
      // 仅恢复由本逻辑触发的折叠，避免覆盖用户手动操作。
      if (h === 0 && autoCollapsedByKeyboard.value) {
        basicInfoCollapsed.value = false
        autoCollapsedByKeyboard.value = false
      }
    }
    uni.onKeyboardHeightChange(keyboardChangeHandler)
  }

  await loadServiceTypes()
  const prefill = JSON.parse(uni.getStorageSync('petstore_report_prefill') || 'null')
  if (prefill) {
    currentAppointmentId.value = prefill.appointmentId
    report.value.petName = prefill.petName || ''
    report.value.serviceType = prefill.serviceType || ''
    if (prefill.appointmentTime) {
      const raw = String(prefill.appointmentTime)
      if (raw.includes('T')) {
        const [d, rest] = raw.split('T')
        const hm = (rest || '').match(/^(\d{1,2}:\d{2})/)?.[1] || DEFAULT_HALF_HOUR_TIME
        report.value.appointmentTime = `${d}T${normalizeToHalfHour(hm)}`
      }
    }
    // 预约数据带入，默认折叠基础信息
    basicInfoCollapsed.value = true
    uni.removeStorageSync('petstore_report_prefill')
  }
})

onUnmounted(() => {
  if (typeof uni.offKeyboardHeightChange === 'function' && keyboardChangeHandler) {
    uni.offKeyboardHeightChange(keyboardChangeHandler)
  }
})

onPageScroll((e) => {
  hasScrolled.value = (e?.scrollTop || 0) > 8
})

const onRemarkFocus = () => {
  remarkFocused.value = true
}

const onRemarkBlur = () => {
  remarkFocused.value = false
}
</script>

<style scoped>
.report-page {
  padding-bottom: calc(96px + env(safe-area-inset-bottom));
  background: #fafaf8;
}
.report-nav {
  padding: 0 16px 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: sticky;
  top: 0;
  z-index: 10;
}

.report-body {
  padding: 12px 16px 20px;
}

/* 区块 */
.section {
  background: #fff;
  border-radius: 16px;
  padding: 20px;
  margin-bottom: 12px;
}
.section-head {
  margin-bottom: 16px;
}
.section-head--clickable {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.section-toggle {
  font-size: 13px;
  font-weight: 600;
  color: var(--c-brand);
}
.section-label {
  font-size: 16px;
  font-weight: 700;
  color: #1a1a1a;
}
.section-hint {
  display: block;
  font-size: 12px;
  color: var(--c-text-3);
  margin-top: 2px;
}

/* 表单 */
.form-row {
  margin-bottom: 14px;
}
.form-row:last-child { margin-bottom: 0; }
.time-row {
  display: flex;
  gap: 10px;
}
.time-picker { flex: 1; }

/* 照片对比区 */
.photo-compare {
  display: flex;
  gap: 0;
}
.photo-side {
  flex: 1;
  min-width: 0;
}
.photo-divider {
  width: 1px;
  background: #ebebea;
  margin: 0 12px;
  align-self: stretch;
}
.photo-side-label {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 10px;
  font-size: 13px;
  font-weight: 600;
  color: #666660;
}
.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
.dot-before { background: #f5913e; }
.dot-after { background: var(--c-brand); }

.photo-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.photo-thumb {
  position: relative;
  width: calc(50% - 4px);
  aspect-ratio: 1;
  border-radius: 10px;
  overflow: hidden;
  background: #f0f0ee;
}
.thumb-img {
  width: 100%;
  height: 100%;
}
.thumb-del {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 10;
  width: 22px;
  height: 22px;
  background: rgba(0,0,0,0.55);
  color: #fff;
  border-radius: 50%;
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-mask {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: rgba(0, 0, 0, 0.42);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  box-sizing: border-box;
}
.media-mask--error {
  background: rgba(188, 46, 46, 0.72);
}
.media-mask-text {
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  text-align: center;
  line-height: 1.3;
}
.photo-add {
  width: calc(50% - 4px);
  aspect-ratio: 1;
  border: 1.5px dashed #ddddd8;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fafaf8;
}

/* 过程中区块 */
.during-add-row {
  margin-bottom: 14px;
}
.during-add-btn {
  height: 44px;
  border-radius: 12px;
  border: 1.5px dashed #ddddd8;
  background: #fafaf8;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: 14px;
  color: var(--c-text-3);
}
.during-add-btn:active {
  background: #f0f0ee;
}

.during-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.video-badge {
  position: absolute;
  bottom: 5px;
  left: 5px;
  height: 18px;
  padding: 0 6px;
  border-radius: 4px;
  background: rgba(0,0,0,0.55);
  display: flex;
  align-items: center;
  justify-content: center;
}
.video-badge text {
  font-size: 10px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.3px;
}

/* 备注 */
.submit-fixed {
  position: fixed;
  left: 0;
  right: 0;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
  background: rgba(250, 250, 248, 0.96);
  border-top: 1px solid #efefeb;
  backdrop-filter: blur(2px);
  z-index: 50;
  transition: box-shadow 0.2s ease, background 0.2s ease, bottom 0.2s ease;
}
.submit-fixed--scrolled {
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
  background: rgba(250, 250, 248, 0.985);
}
.report-lock-hint {
  font-size: 12px;
  color: #8a8a84;
  line-height: 1.45;
  text-align: center;
  margin-bottom: 10px;
  padding: 0 8px;
}
.submit-btn--fixed {
  box-shadow: 0 5px 16px rgba(0, 0, 0, 0.1);
}

/* Loading */
.loading-mask {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
}
.loading-box {
  background: #fff;
  padding: 24px 36px;
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  font-size: 14px;
  color: #666660;
}
.loading-spinner {
  width: 28px;
  height: 28px;
  border: 2.5px solid #ebebea;
  border-top-color: var(--c-brand);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
