<template>
  <!-- 我的预约：见 doc/app/我的预约/我的预约接口说明.md -->
  <view :class="pageRootClass" class="tab-page mb-page" :style="pageStyle">
    <view class="mb-top">
      <view class="mb-card mb-card--tabs">
        <up-tabs
          :current="mainTabIndex"
          class="mb-tabs"
          :list="mainTabsList"
          key-name="name"
          :scrollable="false"
          line-color="#22C55E"
          :line-height="2"
          :item-style="tabsItemStyle"
          :active-style="{ color: '#15803d', fontWeight: '600', fontSize: '13px' }"
          :inactive-style="{ color: '#78716c', fontSize: '13px' }"
          @update:current="onMainTabUpdate"
        />
      </view>
      <scroll-view scroll-x class="mb-chip-scroll" :show-scrollbar="false" enable-flex>
        <view class="mb-chip-row">
          <view
            v-for="chip in subChips"
            :key="chip.key"
            class="mb-chip"
            :class="{ 'mb-chip--on': currentSubKey === chip.key }"
            role="button"
            @tap="setSubKey(chip.key)"
          >
            <text class="mb-chip__txt text-body">{{ $t(chip.labelKey) }}</text>
          </view>
        </view>
      </scroll-view>
    </view>

    <view class="mb-body">
      <view v-if="listLoading && !rows.length" class="mb-empty">
        <text class="text-body mb-empty__txt">{{ $t('myBookingPage.loading') }}</text>
      </view>
      <view v-else-if="!rows.length" class="mb-empty">
        <text class="text-body mb-empty__txt">{{ $t('myBookingPage.empty') }}</text>
      </view>
      <view v-else class="mb-list-wrap">
        <up-virtual-list
          ref="mbVList"
          class="mb-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="mb-cell">
              <view class="mb-item" :style="{ height: rowBodyPx + 'px' }">
                <text class="mb-item__title text-body">{{ titleFor(item) }}</text>
                <text class="mb-item__line text-body">{{ timeLine(item) }}</text>
                <template v-if="item.kind !== 'org'">
                  <text class="mb-item__line text-body">
                    <text class="mb-item__k">{{ $t('myBookingPage.labelAddress') }}</text>
                    {{ addressLine(item) }}
                  </text>
                  <text class="mb-item__line text-body">
                    <text class="mb-item__k">{{ $t('myBookingPage.labelNeed') }}</text>
                    {{ needLine(item) }}
                  </text>
                </template>
                <view class="mb-item__row mb-item__row--status">
                  <text class="mb-item__line text-body">
                    <text class="mb-item__k">{{ $t('myBookingPage.labelStatus') }}</text>
                  </text>
                  <up-tag
                    :text="statusLabel(item)"
                    :type="statusTagType(item)"
                    size="mini"
                    plain
                    plain-fill
                  />
                </view>
                <view class="mb-item__row mb-item__row--end">
                  <!--    :custom-style="cancelBtnStyle" -->
                   <up-button
                    v-if="showCancelBtn(item)"
                    shape="circle"
                    size="small"
                    plain
                    hairline
                    type="primary"
                    :text="$t('myBookingPage.btnCancelBook')"
                    :custom-style="cancelBtnStyle"
                    @click="onCancelBook(item)"
                  />
                  <template v-if="showExpertRateRow(item)">
                  <up-button
                    v-if="!item.hasReview"
                    shape="circle"
                      size="small"
                    type="success"
                     :custom-style="cancelBtnStyle"
                    :text="$t('myBookingPage.btnReview')"
                    @click="onReview(item)"
                  />
                  <up-button
                    v-else
                    shape="circle"
                    size="small"
                    plain
                    hairline
                     :custom-style="cancelBtnStyle"
                    type="success"
                    :text="$t('myBookingPage.btnViewReview')"
                    @click="onViewReview(item)"
                  />
                  </template>
                </view>
              </view>
            </view>
          </template>
        </up-virtual-list>
      </view>
    </view>
  </view>
</template>

<script>
import UTabs from 'uview-plus/components/u-tabs/u-tabs.vue'
import UButton from 'uview-plus/components/u-button/u-button.vue'
import UTag from 'uview-plus/components/u-tag/u-tag.vue'
import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
import tabPage from '@/mixins/tabPage'
import { listMyAppointments, cancelMyAppointment } from '@/api/myAppointment'

const ROW_GAP_RPX = 20
const LIST_PAGE_SIZE = 20
const MB_BODY_PAD_RPX = 40

/** 主 Tab 索引 → providerType：1 兽医 / 3 专家 / 2 机构 */
const PROVIDER_TYPES = [1, 3, 2]

const STATUS_TAB_MAP = {
  all: 'all',
  upcoming: 'notStarted',
  todo_review: 'pendingReview',
  done: 'completed',
  cancelled: 'cancelled'
}

/** 状态码 → up-tag type：0 待确认 / 1 已确认 / 2 已拒绝 / 3 已取消 / 4 已完成 / 5 已预约 */
const STATUS_TAG_TYPE = {
  0: 'warning',
  1: 'primary',
  2: 'error',
  3: 'info',
  4: 'success',
  5: 'primary'
}

function pad2(n) {
  return `${n}`.padStart(2, '0')
}

/** 按主 tab 取列表项内容区高度（rpx），与虚拟列表单项高度一致 */
function rowBodyRpxForTab(mainTabIndex) {
  if (mainTabIndex === 2) return 280
  if (mainTabIndex === 1) return 400
  return 340
}

export default {
  components: {
    'up-tabs': UTabs,
    'up-button': UButton,
    'up-tag': UTag,
    'up-virtual-list': UVirtualList
  },
  mixins: [tabPage],
  data() {
    return {
      navTitleKey: 'myBookingPage.navTitle',
      mainTabIndex: 0,
      vetSubKey: 'all',
      expertSubKey: 'all',
      orgSubKey: 'all',
      rows: [],
      listLoading: false,
      listLoadingMore: false,
      listTotal: 0,
      pageNum: 1,
      pageSize: LIST_PAGE_SIZE,
      loadMoreTimer: null,
      rowBodyPx: 170,
      marginPx: 10,
      slotHeightPx: 180,
      listHeightPx: 400,
      pageHeightPx: 0,
      vScrollTop: 0,
      cancelBtnStyle: 'width:150rpx;flex-shrink:0;padding:0 20rpx;min-width:0;'
    }
  },
  computed: {
    tabsItemStyle() {
      return { height: '32px', padding: '0 8px' }
    },
    mainTabsList() {
      return [
        { name: this.$t('myBookingPage.tabVet') },
        { name: this.$t('myBookingPage.tabExpert') },
        { name: this.$t('myBookingPage.tabOrg') }
      ]
    },
    currentSubKey() {
      if (this.mainTabIndex === 0) return this.vetSubKey
      if (this.mainTabIndex === 1) return this.expertSubKey
      return this.orgSubKey
    },
    subChips() {
      if (this.mainTabIndex === 0) {
        return [
          { key: 'all', labelKey: 'myBookingPage.subAll' },
          { key: 'upcoming', labelKey: 'myBookingPage.subUpcoming' },
          { key: 'done', labelKey: 'myBookingPage.subDone' },
          { key: 'cancelled', labelKey: 'myBookingPage.subCancelled' }
        ]
      }
      if (this.mainTabIndex === 1) {
        return [
          { key: 'all', labelKey: 'myBookingPage.subAll' },
          { key: 'upcoming', labelKey: 'myBookingPage.subUpcoming' },
          { key: 'todo_review', labelKey: 'myBookingPage.subTodoReview' },
          { key: 'done', labelKey: 'myBookingPage.subDone' },
          { key: 'cancelled', labelKey: 'myBookingPage.subCancelled' }
        ]
      }
      return [
        { key: 'all', labelKey: 'myBookingPage.subAll' },
        { key: 'upcoming', labelKey: 'myBookingPage.subUpcoming' },
        { key: 'done', labelKey: 'myBookingPage.subDone' },
        { key: 'cancelled', labelKey: 'myBookingPage.subCancelled' }
      ]
    },
    listNoMore() {
      return this.listTotal > 0 && this.rows.length >= this.listTotal
    },
    pageStyle() {
      if (this.pageHeightPx > 0) {
        return { height: `${this.pageHeightPx}px` }
      }
      return {}
    }
  },
  watch: {
    mainTabIndex() {
      this.vScrollTop = 0
      this.applyRowMetrics()
      this.loadList(true)
      this.$nextTick(() => {
        this.calcLayoutHeights()
      })
    }
  },
  onShow() {
    this.loadList(true)
  },
  onReady() {
    this.applyRowMetrics()
    this.applyListHeightFallback()
    this.$nextTick(() => this.calcLayoutHeights())
  },
  onUnload() {
    if (this.loadMoreTimer) clearTimeout(this.loadMoreTimer)
  },
  methods: {
    providerTypeForTab() {
      return PROVIDER_TYPES[this.mainTabIndex] || 1
    },
    statusTabForSub() {
      return STATUS_TAB_MAP[this.currentSubKey] || 'all'
    },
    mapRow(row) {
      const pt = Number(row.providerType)
      let kind = 'vet'
      if (pt === 2) kind = 'org'
      else if (pt === 3) kind = 'expert'
      return {
        id: row.id,
        kind,
        providerType: pt,
        providerName: row.providerName || '',
        appointDate: row.appointDate || '',
        timeStart: row.timeStart || '',
        timeEnd: row.timeEnd || '',
        serviceAddress: row.serviceAddress || '',
        serviceRequirement: row.serviceRequirement || '',
        status: row.status,
        statusLabel: row.statusLabel || '',
        canCancel: !!row.canCancel,
        hasReview: !!row.hasReview,
        rejectReason: row.rejectReason || ''
      }
    },
    loadList(reset = false) {
      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 listMyAppointments({
        providerType: this.providerTypeForTab(),
        statusTab: this.statusTabForSub(),
        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) => this.mapRow(row))
          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
          }
        })
    },
    tryAutoLoadMore() {
      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.loadList(false)
      }
    },
    onVirtualListScroll(scrollTop) {
      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.loadList(false)
        return
      }
      if (scrollTop + viewH >= totalH - threshold) {
        this.loadList(false)
      }
    },
    applyRowMetrics() {
      try {
        const rpx = rowBodyRpxForTab(this.mainTabIndex)
        this.marginPx = Math.ceil(uni.upx2px(ROW_GAP_RPX))
        this.rowBodyPx = Math.max(120, Math.ceil(uni.upx2px(rpx)))
        this.slotHeightPx = this.rowBodyPx + this.marginPx
      } catch (e) {
        this.marginPx = 10
        this.rowBodyPx = 170
        this.slotHeightPx = 180
      }
    },
    applyListHeightFallback() {
      const contentH = this.getViewportContentHeight()
      const topFallback = Math.ceil(uni.upx2px(200))
      const bodyPad = Math.ceil(uni.upx2px(MB_BODY_PAD_RPX))
      this.pageHeightPx = contentH
      this.listHeightPx = Math.max(240, contentH - topFallback - 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()
      const bodyPad = Math.ceil(uni.upx2px(MB_BODY_PAD_RPX))
      this.pageHeightPx = contentH

      uni.createSelectorQuery()
        .in(this)
        .select('.mb-top')
        .boundingClientRect((topRect) => {
          const topH =
            topRect && topRect.height > 0 ? Math.ceil(topRect.height) : Math.ceil(uni.upx2px(200))
          this.listHeightPx = Math.max(240, contentH - topH - bodyPad)
          this.$nextTick(() => {
            this.$refs.mbVList?.measureContainerHeight?.()
            this.tryAutoLoadMore()
          })
        })
        .exec()
    },
    onMainTabUpdate(idx) {
      this.mainTabIndex = typeof idx === 'number' ? idx : 0
    },
    setSubKey(key) {
      if (this.mainTabIndex === 0) this.vetSubKey = key
      else if (this.mainTabIndex === 1) this.expertSubKey = key
      else this.orgSubKey = key
      this.vScrollTop = 0
      this.loadList(true)
    },
    titleFor(item) {
      return item.providerName || '—'
    },
    parseAppointDate(dateStr) {
      if (!dateStr) return null
      const d = new Date(String(dateStr).replace(/-/g, '/'))
      return isNaN(d.getTime()) ? null : d
    },
    timeLine(item) {
      const d = this.parseAppointDate(item.appointDate)
      if (!d) {
        return this.$t('myBookingPage.timeTpl', {
          md: '—',
          wd: '',
          start: item.timeStart || '--:--',
          end: item.timeEnd || '--:--'
        })
      }
      const m = pad2(d.getMonth() + 1)
      const day = pad2(d.getDate())
      const wd = this.$t(`bookingServicePage.wd${d.getDay()}`)
      return this.$t('myBookingPage.timeTpl', {
        md: `${m}-${day}`,
        wd,
        start: item.timeStart || '--:--',
        end: item.timeEnd || '--:--'
      })
    },
    addressLine(item) {
      const s = (item.serviceAddress || '').trim()
      return s || this.$t('myBookingPage.noAddress')
    },
    needLine(item) {
      const s = (item.serviceRequirement || '').trim()
      return s || this.$t('myBookingPage.noNeed')
    },
    statusLabel(item) {
      if (item.statusLabel) return item.statusLabel
      const st = Number(item.status)
      const map = {
        0: 'myBookingPage.vetStatusPending',
        1: 'myBookingPage.vetStatusConfirmed',
        2: 'myBookingPage.vetStatusRejected',
        3: 'myBookingPage.vetStatusCancelled',
        4: 'myBookingPage.vetStatusCompleted',
        5: 'myBookingPage.orgStatusBooked'
      }
      return this.$t(map[st] || map[0])
    },
    statusTagType(item) {
      const st = Number(item.status)
      return STATUS_TAG_TYPE[st] || 'info'
    },
    showCancelBtn(item) {
      return !!item.canCancel
    },
    showExpertRateRow(item) {
      return item.kind === 'expert' && Number(item.status) === 4
    },
    onCancelBook(item) {
      uni.showModal({
        title: this.$t('myBookingPage.confirmCancelTitle'),
        content: this.$t('myBookingPage.confirmCancelBody'),
        confirmText: this.$t('myBookingPage.confirmOk'),
        cancelText: this.$t('myBookingPage.confirmNo'),
        success: (res) => {
          if (!res.confirm) return
          cancelMyAppointment(item.id)
            .then(() => {
              uni.showToast({ title: this.$t('myBookingPage.toastCancelled'), icon: 'none' })
              this.loadList(true)
            })
            .catch(() => {
              uni.showToast({ title: this.$t('myBookingPage.cancelFail'), icon: 'none' })
            })
        }
      })
    },
    reviewNavUrl(item, mode) {
      const q = [
        `appointmentId=${encodeURIComponent(item.id)}`,
        `mode=${encodeURIComponent(mode)}`,
        `providerName=${encodeURIComponent(item.providerName || '')}`
      ].join('&')
      return `/package-a/review-history/index?${q}`
    },
    onReview(item) {
      uni.navigateTo({ url: this.reviewNavUrl(item, 'submit') })
    },
    onViewReview(item) {
      uni.navigateTo({ url: this.reviewNavUrl(item, 'view') })
    }
  }
}
</script>

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

.mb-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;
}

.mb-top {
  flex-shrink: 0;
  min-width: 0;
}

.mb-card {
  background: #ffffff;
  padding: 16rpx 20rpx;
  box-sizing: border-box;
}

.mb-card--tabs {
  padding: 8rpx 12rpx 4rpx;
}

.mb-tabs {
  width: 100%;
}

.mb-tabs :deep(.u-tabs__wrapper__nav__item__text) {
  line-height: 1.2;
}

.mb-chip-scroll {
  width: 100%;
  white-space: nowrap;
  background: #ffffff;
  border-bottom: 1rpx solid $morandi-border-soft;
}

.mb-chip-row {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 16rpx;
  padding: 12rpx 20rpx 16rpx;
  min-width: min-content;
}

.mb-chip {
  flex-shrink: 0;
  padding: 10rpx 22rpx;
  border-radius: 999rpx;
  border: 1rpx solid #d6d3d1;
  background: #fafaf9;
}

.mb-chip--on {
  border-color: #22c55e;
  background: rgba(34, 197, 94, 0.08);
}

.mb-chip__txt {
  font-size: 24rpx;
  color: #78716c;
  font-weight: 500;
}

.mb-chip--on .mb-chip__txt {
  color: #15803d;
  font-weight: 600;
}

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

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

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

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

.mb-vlist {
  flex: 1;
  min-height: 0;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  padding: 10rpx 0 0;
}

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

.mb-item {
  display: flex;
  flex-direction: column;
  gap: 8rpx;
  padding: 20rpx;
  box-sizing: border-box;
  background: #ffffff;
  border-radius: 12rpx;
  border: 1rpx solid $morandi-border-soft;
  min-width: 0;
}

.mb-item__title {
  font-size: 30rpx;
  font-weight: 600;
  color: #111827;
  line-height: 1.35;
  word-break: break-word;
}

.mb-item__line {
  font-size: 24rpx;
  color: $morandi-text-muted;
  line-height: 1.45;
  word-break: break-word;
}

.mb-item__line--grow {
  flex: 1;
  min-width: 0;
}

.mb-item__k {
  color: $morandi-text-muted;
  font-weight: 500;
}

.mb-item__row {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 12rpx;
  margin-top: 4rpx;
  min-width: 0;
}

.mb-item__row--status {
  justify-content: flex-start;
  gap: 8rpx;
}

.mb-item__row--status :deep(.u-tag) {
  flex-shrink: 0;
}

.mb-item__row--end {
  width: 100%;
  justify-content: flex-end;
  flex-wrap: wrap;
  margin-top: 8rpx;
}

/* up-button 根节点默认易撑满行宽，需收窄后 flex-end 才能靠右 */
.mb-item__row--end :deep(.u-button) {
  width: auto !important;
  flex-shrink: 0;
}

.mb-page.lang-bo {
  .mb-item__title {
    font-size: 26rpx;
    line-height: 1.65;
  }

  .mb-item__line {
    font-size: 22rpx;
  }
}
</style>
