<template>
  <!-- 历史评价：见 doc/app/预约服务/预约服务接口说明.md §4；我的预约评价见 myAppointment -->
  <view :class="pageRootClass" class="tab-page rh-page" :style="pageStyle">
    <!-- 提交评价（我的预约 → 专家已完成） -->
    <scroll-view
      v-if="pageMode === 'appointmentSubmit'"
      scroll-y
      class="rh-submit-scroll"
      enable-back-to-top
      :show-scrollbar="false"
    >
      <view class="rh-card rh-submit-card">
        <text v-if="providerName" class="rh-submit-org text-body">{{ providerName }}</text>
        <view v-for="dim in scoreDims" :key="dim.key" class="rh-score-row">
          <text class="rh-score-label text-body">{{ $t(dim.labelKey) }}</text>
          <up-rate
            v-model="form[dim.key]"
            :count="5"
            :size="22"
            gutter="6"
            active-color="#EAB308"
            inactive-color="#d1d5db"
          />
        </view>
        <text class="rh-score-label text-body rh-score-label--block">{{ $t('reviewHistoryPage.labelSummary') }}</text>
        <up-textarea
          v-model="form.summary"
          height="140"
          count
          maxlength="100"
          border="surround"
          :placeholder="$t('reviewHistoryPage.summaryPh')"
        />
      </view>
      <view class="rh-submit-actions">
        <up-button
          type="success"
          shape="circle"
          :loading="submitting"
          :text="submitting ? $t('reviewHistoryPage.submitting') : $t('reviewHistoryPage.btnSubmit')"
          @click="onSubmitReview"
        />
      </view>
      <view class="rh-footer-spacer" />
    </scroll-view>

    <!-- 评价列表 / 单条查看 -->
    <view v-else class="rh-body">
      <view v-if="listLoading && !rows.length" class="rh-empty">
        <text class="text-body rh-empty__txt">{{ $t('reviewHistoryPage.loading') }}</text>
      </view>
      <view v-else-if="!rows.length" class="rh-empty">
        <text class="text-body rh-empty__txt">{{ $t('reviewHistoryPage.empty') }}</text>
      </view>
      <view v-else class="rh-list-wrap">
        <up-virtual-list
          ref="rhVList"
          class="rh-vlist"
          :list-data="rows"
          :item-height="slotHeightPx"
          :height="listHeightPx"
          :buffer="6"
          key-field="id"
          :scroll-top="vScrollTop"
          @update:scrollTop="vScrollTop = $event"
          @scroll="onVirtualListScroll"
        >
          <template #default="{ item }">
            <view class="rh-cell">
              <view class="rh-card" :style="{ height: rowBodyPx + 'px' }">
                <view class="rh-row">
                  <up-avatar
                    shape="circle"
                    :text="item.avatarText"
                    size="48"
                    font-size="18"
                    bg-color="#9ca3af"
                    color="#ffffff"
                  />
                  <view class="rh-right">
                    <text class="rh-name text-body">{{ item.reviewerName }}</text>
                    <text class="rh-date text-body">{{ item.dateStr }}</text>
                    <view class="rh-scores">
                      <view v-for="dim in scoreDims" :key="dim.key" class="rh-score-item">
                        <text class="rh-score-item__label text-body">{{ $t(dim.labelKey) }}</text>
                        <up-rate
                          :model-value="scoreForItem(item, dim.key)"
                          readonly
                          :touchable="false"
                          :count="5"
                          :size="12"
                          gutter="2"
                          active-color="#EAB308"
                          inactive-color="#d1d5db"
                        />
                      </view>
                    </view>
                    <text class="rh-content text-body">{{ item.summary }}</text>
                  </view>
                </view>
              </view>
            </view>
          </template>
        </up-virtual-list>
      </view>
    </view>
  </view>
</template>

<script>
import UAvatar from 'uview-plus/components/u-avatar/u-avatar.vue'
import UButton from 'uview-plus/components/u-button/u-button.vue'
import URate from 'uview-plus/components/u-rate/u-rate.vue'
import UTextarea from 'uview-plus/components/u-textarea/u-textarea.vue'
import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
import tabPage from '@/mixins/tabPage'
import { ensureApiToken } from '@/utils/apiAuth'
import { listExpertBookingReviews } from '@/api/bookingService'
import { getMyAppointmentReview, submitMyAppointmentReview } from '@/api/myAppointment'

const ROW_BODY_RPX = 400
const ROW_GAP_RPX = 20
const LIST_PAGE_SIZE = 20
const RH_BODY_PAD_RPX = 40

const SCORE_DIMS = [
  { key: 'punctualityScore', labelKey: 'reviewHistoryPage.scorePunctuality' },
  { key: 'attitudeScore', labelKey: 'reviewHistoryPage.scoreAttitude' },
  { key: 'guidanceScore', labelKey: 'reviewHistoryPage.scoreGuidance' }
]

function normalizeScore(raw) {
  const v = Number(raw)
  if (!Number.isFinite(v) || v <= 0) return 0
  return Math.min(5, Math.max(0, Math.round(v)))
}

function formatReviewTime(raw) {
  if (!raw) return ''
  const s = String(raw).trim()
  if (s.length >= 16) return s.slice(0, 16)
  if (s.length >= 10) return s.slice(0, 10)
  return s
}

export default {
  components: {
    'up-avatar': UAvatar,
    'up-button': UButton,
    'up-rate': URate,
    'up-textarea': UTextarea,
    'up-virtual-list': UVirtualList
  },
  mixins: [tabPage],
  data() {
    return {
      pageMode: 'expertList',
      expertId: '',
      appointmentId: '',
      providerName: '',
      rows: [],
      listLoading: false,
      listLoadingMore: false,
      listTotal: 0,
      pageNum: 1,
      pageSize: LIST_PAGE_SIZE,
      loadMoreTimer: null,
      submitting: false,
      form: {
        punctualityScore: 5,
        attitudeScore: 5,
        guidanceScore: 5,
        summary: ''
      },
      rowBodyPx: 120,
      marginPx: 10,
      slotHeightPx: 130,
      listHeightPx: 400,
      pageHeightPx: 0,
      vScrollTop: 0
    }
  },
  computed: {
    scoreDims() {
      return SCORE_DIMS
    },
    navTitleKey() {
      if (this.pageMode === 'appointmentSubmit') return 'reviewHistoryPage.navTitleSubmit'
      if (this.pageMode === 'appointmentView') return 'reviewHistoryPage.navTitleView'
      return 'reviewHistoryPage.navTitle'
    },
    listNoMore() {
      return this.listTotal > 0 && this.rows.length >= this.listTotal
    },
    pageStyle() {
      if (this.pageHeightPx > 0) {
        return { height: `${this.pageHeightPx}px` }
      }
      return {}
    }
  },
  onLoad(query) {
    if (!ensureApiToken()) return
    const q = query || {}
    this.appointmentId = q.appointmentId ? decodeURIComponent(String(q.appointmentId)) : ''
    this.providerName = q.providerName ? decodeURIComponent(String(q.providerName)) : ''
    const mode = q.mode ? decodeURIComponent(String(q.mode)) : 'view'
    this.expertId = q.id ? decodeURIComponent(String(q.id)) : ''

    if (this.appointmentId) {
      this.pageMode = mode === 'submit' ? 'appointmentSubmit' : 'appointmentView'
    } else if (this.expertId) {
      this.pageMode = 'expertList'
    } else {
      this.pageMode = 'expertList'
    }

    if (this.pageMode === 'expertList' && this.expertId) {
      this.loadExpertReviews(true)
    } else if (this.pageMode === 'appointmentView') {
      this.loadAppointmentReview()
    }
  },
  onReady() {
    try {
      this.marginPx = Math.ceil(uni.upx2px(ROW_GAP_RPX))
      this.rowBodyPx = Math.max(100, Math.ceil(uni.upx2px(ROW_BODY_RPX)))
      this.slotHeightPx = this.rowBodyPx + this.marginPx
    } catch (e) {
      this.marginPx = 10
      this.rowBodyPx = 120
      this.slotHeightPx = 130
    }
    if (this.pageMode !== 'appointmentSubmit') {
      this.applyListHeightFallback()
      this.$nextTick(() => this.calcLayoutHeights())
    } else {
      this.applyListHeightFallback()
    }
  },
  onUnload() {
    if (this.loadMoreTimer) clearTimeout(this.loadMoreTimer)
  },
  methods: {
    mapReviewRow(row, idx) {
      const name = (row.reviewerName || '').trim() || this.$t('reviewHistoryPage.anonymous')
      return {
        id: row.id != null ? String(row.id) : `r-${idx}`,
        reviewerName: name,
        avatarText: name.slice(0, 1) || '?',
        dateStr: formatReviewTime(row.reviewTime),
        punctualityScore: normalizeScore(row.punctualityScore),
        attitudeScore: normalizeScore(row.attitudeScore),
        guidanceScore: normalizeScore(row.guidanceScore),
        summary: (row.summary || '').trim() || '—'
      }
    },
    scoreForItem(item, key) {
      if (!item || !key) return 0
      return normalizeScore(item[key])
    },
    loadExpertReviews(reset = false) {
      if (!this.expertId) return Promise.resolve()
      if (!reset) {
        if (this.listLoading || this.listLoadingMore || this.listNoMore) {
          return Promise.resolve()
        }
        this.pageNum += 1
        this.listLoadingMore = true
      } else {
        this.pageNum = 1
        this.listLoading = true
      }
      return listExpertBookingReviews(this.expertId, {
        pageNum: this.pageNum,
        pageSize: this.pageSize
      })
        .then((res) => {
          const raw = res.rows || []
          this.listTotal = res.total != null ? Number(res.total) : 0
          const mapped = raw.map((row, idx) => this.mapReviewRow(row, idx))
          this.rows = reset ? mapped : this.rows.concat(mapped)
          this.$nextTick(() => {
            this.calcLayoutHeights()
          })
        })
        .catch(() => {
          if (reset) {
            this.rows = []
            this.listTotal = 0
          } else {
            this.pageNum -= 1
          }
        })
        .finally(() => {
          if (reset) {
            this.listLoading = false
          } else {
            this.listLoadingMore = false
          }
        })
    },
    loadAppointmentReview() {
      if (!this.appointmentId) return
      this.listLoading = true
      getMyAppointmentReview(this.appointmentId)
        .then((res) => {
          const data = res.data
          if (data && typeof data === 'object') {
            this.rows = [this.mapReviewRow(data, 0)]
            this.listTotal = 1
          } else {
            this.rows = []
            this.listTotal = 0
          }
          this.$nextTick(() => this.calcLayoutHeights())
        })
        .catch(() => {
          this.rows = []
          this.listTotal = 0
        })
        .finally(() => {
          this.listLoading = false
        })
    },
    validateSubmitForm() {
      for (const dim of SCORE_DIMS) {
        const v = Number(this.form[dim.key])
        if (!Number.isFinite(v) || v < 1 || v > 5) {
          uni.showToast({ title: this.$t('reviewHistoryPage.errScore', { label: this.$t(dim.labelKey) }), icon: 'none' })
          return false
        }
      }
      const summary = (this.form.summary || '').trim()
      if (!summary) {
        uni.showToast({ title: this.$t('reviewHistoryPage.errSummary'), icon: 'none' })
        return false
      }
      if (summary.length > 100) {
        uni.showToast({ title: this.$t('reviewHistoryPage.errSummaryLen'), icon: 'none' })
        return false
      }
      return true
    },
    onSubmitReview() {
      if (this.submitting || !this.appointmentId) return
      if (!this.validateSubmitForm()) return
      this.submitting = true
      submitMyAppointmentReview(this.appointmentId, {
        punctualityScore: Number(this.form.punctualityScore),
        attitudeScore: Number(this.form.attitudeScore),
        guidanceScore: Number(this.form.guidanceScore),
        summary: (this.form.summary || '').trim()
      })
        .then(() => {
          uni.showToast({ title: this.$t('reviewHistoryPage.toastSubmitOk'), icon: 'success' })
          setTimeout(() => uni.navigateBack(), 800)
        })
        .catch(() => {})
        .finally(() => {
          this.submitting = false
        })
    },
    tryAutoLoadMore() {
      if (this.pageMode !== 'expertList') return
      if (this.listLoading || this.listLoadingMore || this.listNoMore) return
      const totalH = this.rows.length * this.slotHeightPx
      const viewH = this.listHeightPx
      if (totalH > 0 && totalH <= viewH) {
        this.loadExpertReviews(false)
      }
    },
    onVirtualListScroll(scrollTop) {
      if (this.pageMode !== 'expertList') return
      if (this.loadMoreTimer) clearTimeout(this.loadMoreTimer)
      this.loadMoreTimer = setTimeout(() => {
        this.loadMoreTimer = null
        this.tryLoadMoreOnScroll(typeof scrollTop === 'number' ? scrollTop : 0)
      }, 180)
    },
    tryLoadMoreOnScroll(scrollTop) {
      if (this.listLoading || this.listLoadingMore || this.listNoMore) return
      const totalH = this.rows.length * this.slotHeightPx
      const viewH = this.listHeightPx
      const threshold = Math.max(this.slotHeightPx * 2, 120)
      if (totalH <= viewH) {
        this.loadExpertReviews(false)
        return
      }
      if (scrollTop + viewH >= totalH - threshold) {
        this.loadExpertReviews(false)
      }
    },
    applyListHeightFallback() {
      const contentH = this.getViewportContentHeight()
      const bodyPad = Math.ceil(uni.upx2px(RH_BODY_PAD_RPX))
      this.pageHeightPx = contentH
      this.listHeightPx = Math.max(280, contentH - bodyPad)
    },
    getNavigationBarHeight() {
      const sys = uni.getSystemInfoSync()
      const statusBar = sys.statusBarHeight || 0
      // #ifdef MP-WEIXIN
      try {
        const menu = uni.getMenuButtonBoundingClientRect()
        if (menu && menu.height) {
          return statusBar + (menu.top - statusBar) * 2 + menu.height
        }
      } catch (e) {
        /* noop */
      }
      // #endif
      return statusBar + 44
    },
    getViewportContentHeight() {
      const sys = uni.getSystemInfoSync()
      const navH = this.getNavigationBarHeight()
      // #ifdef H5
      if (typeof window !== 'undefined' && window.innerHeight) {
        return Math.max(320, window.innerHeight - navH)
      }
      // #endif
      const screenH = sys.screenHeight || sys.windowHeight || 600
      return Math.max(320, screenH - navH)
    },
    calcLayoutHeights() {
      const contentH = this.getViewportContentHeight()
      this.pageHeightPx = contentH

      uni.createSelectorQuery()
        .in(this)
        .select('.rh-body')
        .boundingClientRect((rect) => {
          if (rect && rect.height > 0) {
            this.listHeightPx = Math.max(280, Math.floor(rect.height))
          } else {
            const bodyPad = Math.ceil(uni.upx2px(RH_BODY_PAD_RPX))
            this.listHeightPx = Math.max(280, contentH - bodyPad)
          }
          this.$nextTick(() => {
            this.$refs.rhVList?.measureContainerHeight?.()
            this.tryAutoLoadMore()
          })
        })
        .exec()
    }
  }
}
</script>

<style lang="scss" scoped>
@import '@/styles/morandi.scss';
@import '@/styles/tab-page.scss';

.rh-page {
  display: flex;
  flex-direction: column;
  min-width: 0;
  width: 100%;
  height: 100%;
  min-height: 100%;
  overflow: hidden;
  box-sizing: border-box;
  background: $morandi-bg-page;
}

.rh-body {
  flex: 1;
  min-height: 0;
  height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: 16rpx 24rpx 24rpx;
  box-sizing: border-box;
  overflow: hidden;
}

.rh-list-wrap {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.rh-submit-scroll {
  flex: 1;
  min-height: 0;
  min-width: 0;
  height: 0;
  box-sizing: border-box;
  padding: 20rpx 24rpx 24rpx;
}

.rh-submit-card {
  margin-bottom: 24rpx;
}

.rh-submit-org {
  display: block;
  font-size: 30rpx;
  font-weight: 600;
  color: #111827;
  margin-bottom: 24rpx;
  word-break: break-word;
}

.rh-score-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 16rpx;
  margin-bottom: 20rpx;
  min-width: 0;
}

.rh-score-label {
  font-size: 26rpx;
  color: #374151;
  flex-shrink: 0;
}

.rh-score-label--block {
  display: block;
  margin: 8rpx 0 12rpx;
}

.rh-submit-actions {
  padding: 0 8rpx;
}

.rh-footer-spacer {
  height: 32rpx;
}

.rh-empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: 80rpx 24rpx;
  box-sizing: border-box;
}

.rh-empty__txt {
  color: $morandi-text-muted;
}

.rh-vlist {
  flex: 1;
  min-height: 0;
  width: 100%;
  height: 100%;
}

.rh-cell {
  box-sizing: border-box;
  padding-bottom: 10rpx;
}

.rh-card {
  padding: 20rpx;
  box-sizing: border-box;
  background: #ffffff;
  border-radius: 12rpx;
  border: 1rpx solid $morandi-border-soft;
  min-width: 0;
}

.rh-row {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 16rpx;
  min-width: 0;
}

.rh-right {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8rpx;
}

.rh-name {
  font-size: 28rpx;
  font-weight: 600;
  color: #111827;
  line-height: 1.35;
  word-break: break-word;
}

.rh-date {
  font-size: 22rpx;
  color: $morandi-text-muted;
  line-height: 1.4;
}

.rh-scores {
  display: flex;
  flex-direction: column;
  gap: 6rpx;
  min-width: 0;
}

.rh-score-item {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12rpx;
  min-width: 0;
}

.rh-score-item__label {
  flex-shrink: 0;
  width: 128rpx;
  font-size: 22rpx;
  color: $morandi-text-muted;
  line-height: 1.35;
}

.rh-content {
  font-size: 24rpx;
  color: $morandi-text-secondary;
  line-height: 1.5;
  word-break: break-word;
}

.rh-page.lang-bo {
  .rh-name {
    font-size: 26rpx;
    line-height: 1.75;
  }
}
</style>
