<template>
  <!-- 预约服务：见 doc/app/预约服务/预约服务接口说明.md -->
  <view :class="pageRootClass" class="tab-page bs-page" :style="pageStyle">
    <view class="bs-top">
      <view class="bs-card bs-card--flush">
        <scroll-view scroll-x class="bs-date-scroll" :show-scrollbar="false" enable-flex>
          <view class="bs-date-row">
            <view
              v-for="chip in dateChips"
              :key="chip.key"
              class="bs-chip"
              :class="{ 'bs-chip--on': selectedDateKey === chip.key }"
              role="button"
              @tap="onDateChipTap(chip.key)"
            >
              <template v-if="chip.key === 'all'">
                <text class="bs-chip__all text-body">{{ $t('bookingServicePage.all') }}</text>
              </template>
              <view v-else class="bs-chip__stack">
                <text class="bs-chip__name text-body">{{ chip.weekdayLabel }}</text>
                <text class="bs-chip__date text-body">{{ chip.dateShort }}</text>
              </view>
            </view>
          </view>
        </scroll-view>
      </view>

      <view class="bs-card border-top">
        <up-search
          v-model="searchKeyword"
          shape="round"
          :placeholder="$t('bookingServicePage.searchPlaceholder')"
          :show-action="false"
          :clearabled="true"
          bg-color="#f5f5f5"
          border-color="#e8e8e8"
        />
      </view>

      <view class="bs-card bs-card--tabs">
        <up-tabs
          :current="tabIndex"
          class="bs-tabs"
          :list="tabsList"
          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="onTabUpdate"
        />
      </view>
    </view>

    <view class="bs-body">
      <view v-if="listLoading && !resourceRows.length" class="bs-empty">
        <text class="text-body bs-empty__txt">{{ $t('bookingServicePage.loading') }}</text>
      </view>
      <view v-else-if="!resourceRows.length" class="bs-empty">
        <text class="text-body bs-empty__txt">{{ $t('bookingServicePage.empty') }}</text>
      </view>
      <view v-else class="bs-list-wrap">
        <up-virtual-list
          ref="bsVList"
          class="bs-vlist"
          :list-data="resourceRows"
          :item-height="slotHeightPx"
          :height="listHeightPx"
          :buffer="6"
          key-field="id"
          :scroll-top="vScrollTop"
          @update:scrollTop="vScrollTop = $event"
          @scroll="onVirtualListScroll"
        >
          <template #default="{ item }">
            <view class="bs-cell">
              <view class="bs-item" :style="{ height: rowBodyPx + 'px' }">
                <view class="bs-item__row1">
                  <up-avatar
                    shape="circle"
                    :src="avatarUrl(item)"
                    :text="avatarUrl(item) ? '' : item.avatarText"
                    size="48"
                    font-size="18"
                    bg-color="#9ca3af"
                    color="#ffffff"
                  />
                  <view class="bs-item__right">
                    <text class="bs-item__title text-body">{{ titleFor(item) }}</text>
                    <text class="bs-item__sub text-body">{{ subFor(item) }}</text>
                    <text class="bs-item__intro text-body">{{ introFor(item) }}</text>
                  </view>
                </view>
                <up-button
                  type="success"
                  :text="$t('bookingServicePage.btnBook')"
                  :custom-style="{ width: '100%' }"
                  @click="onBook(item)"
                />
              </view>
            </view>
          </template>
        </up-virtual-list>
      </view>
    </view>
  </view>
</template>

<script>
import USearch from 'uview-plus/components/u-search/u-search.vue'
import UTabs from 'uview-plus/components/u-tabs/u-tabs.vue'
import UAvatar from 'uview-plus/components/u-avatar/u-avatar.vue'
import UButton from 'uview-plus/components/u-button/u-button.vue'
import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
import tabPage from '@/mixins/tabPage'
import { resolveResourceUrl } from '@/utils/resourceUrl'
import { ensureApiToken } from '@/utils/apiAuth'
import {
  listBookingDates,
  listBookingResources,
  saveBookingResourceCache
} from '@/api/bookingService'

const ROW_BODY_RPX = 260
const ROW_GAP_RPX = 20
const LIST_PAGE_SIZE = 20
const BS_BODY_PAD_RPX = 40
const RESOURCE_TYPES = ['004001', '004005', '004003']
const BOOKING_PATHS = {
  '004001': '/package-a/booking-vet/index',
  '004005': '/package-a/booking-expert/index',
  '004003': '/package-a/booking-org/index'
}

export default {
  components: {
    'up-search': USearch,
    'up-tabs': UTabs,
    'up-avatar': UAvatar,
    'up-button': UButton,
    'up-virtual-list': UVirtualList
  },
  mixins: [tabPage],
  data() {
    return {
      navTitleKey: 'bookingServicePage.navTitle',
      searchKeyword: '',
      tabIndex: 0,
      selectedDateKey: 'all',
      apiDates: [],
      resourceRows: [],
      listLoading: false,
      listLoadingMore: false,
      listTotal: 0,
      pageNum: 1,
      pageSize: LIST_PAGE_SIZE,
      rowBodyPx: 120,
      marginPx: 10,
      slotHeightPx: 130,
      listHeightPx: 400,
      pageHeightPx: 0,
      vScrollTop: 0,
      loadMoreTimer: null,
      _searchTimer: null
    }
  },
  computed: {
    tabsItemStyle() {
      return { height: '32px', padding: '0 8px' }
    },
    currentResourceType() {
      return RESOURCE_TYPES[this.tabIndex] || RESOURCE_TYPES[0]
    },
    selectedWeekday() {
      if (this.selectedDateKey === 'all') return undefined
      const chip = this.dateChips.find((c) => c.key === this.selectedDateKey)
      return chip && chip.weekday != null ? chip.weekday : undefined
    },
    dateChips() {
      const chips = [
        {
          key: 'all',
          dateShort: '',
          weekdayLabel: '',
          weekday: null
        }
      ]
      for (const d of this.apiDates) {
        chips.push({
          key: d.appointDate || d.key,
          dateShort: d.dateMmDd || '',
          weekday: d.weekday,
          weekdayLabel: this.weekdayLabelFor(d)
        })
      }
      return chips
    },
    tabsList() {
      return [
        { name: this.$t('bookingServicePage.tabVet') },
        { name: this.$t('bookingServicePage.tabExpert') },
        { name: this.$t('bookingServicePage.tabOrg') }
      ]
    },
    listNoMore() {
      return this.listTotal > 0 && this.resourceRows.length >= this.listTotal
    },
    pageStyle() {
      if (this.pageHeightPx > 0) {
        return { height: `${this.pageHeightPx}px` }
      }
      return {}
    }
  },
  watch: {
    searchKeyword() {
      this.vScrollTop = 0
      clearTimeout(this._searchTimer)
      this._searchTimer = setTimeout(() => {
        this.loadResourceList(true)
      }, 300)
    },
    tabIndex() {
      this.vScrollTop = 0
      this.loadResourceList(true)
    },
    selectedDateKey() {
      this.vScrollTop = 0
      this.loadResourceList(true)
    }
  },
  onShow() {
    if (ensureApiToken(false)) {
      this.loadBookingDates()
      this.loadResourceList(true)
    }
  },
  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
    }
    this.applyListHeightFallback()
    this.$nextTick(() => this.calcLayoutHeights())
  },
  onUnload() {
    if (this.loadMoreTimer) clearTimeout(this.loadMoreTimer)
    if (this._searchTimer) clearTimeout(this._searchTimer)
  },
  methods: {
    weekdayLabelFor(d) {
      const w = d && d.weekday
      if (w != null && w >= 1 && w <= 7) {
        const dowKey = w === 7 ? 0 : w
        return this.$t(`bookingServicePage.wd${dowKey}`)
      }
      return (d && d.weekdayName) || ''
    },
    loadBookingDates() {
      if (!ensureApiToken(false)) return
      listBookingDates()
        .then((res) => {
          this.apiDates = Array.isArray(res.data) ? res.data : []
        })
        .catch(() => {
          this.apiDates = []
        })
    },
    mapResourceRow(row) {
      const name = row.resourceName || ''
      const type = row.resourceType || this.currentResourceType
      let kind = 'vet'
      if (type === '004005') kind = 'expert'
      else if (type === '004003') kind = 'org'
      return {
        id: String(row.id != null ? row.id : ''),
        kind,
        resourceType: type,
        resourceName: name,
        affiliatedUnit: row.affiliatedUnit || '',
        introduction: row.introduction || '',
        expertRating: row.expertRating,
        photoFileUrl: row.photoFileUrl || '',
        avatarText: name.slice(0, 1) || '?',
        raw: row
      }
    },
    loadResourceList(reset = false) {
      if (!ensureApiToken()) 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
      }
      const params = {
        resourceType: this.currentResourceType,
        pageNum: this.pageNum,
        pageSize: this.pageSize
      }
      const kw = (this.searchKeyword || '').trim()
      if (kw) {
        params.resourceName = kw
      }
      if (this.selectedWeekday != null) {
        params.weekday = this.selectedWeekday
      }
      return listBookingResources(params)
        .then((res) => {
          const rows = (res.rows || []).map((row) => this.mapResourceRow(row))
          this.listTotal = res.total != null ? Number(res.total) : 0
          this.resourceRows = reset ? rows : this.resourceRows.concat(rows)
          this.$nextTick(() => {
            this.calcLayoutHeights()
          })
        })
        .catch(() => {
          if (reset) {
            this.resourceRows = []
            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.resourceRows.length * this.slotHeightPx
      const viewH = this.listHeightPx
      if (totalH > 0 && totalH <= viewH) {
        this.loadResourceList(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.resourceRows.length * this.slotHeightPx
      const viewH = this.listHeightPx
      const threshold = Math.max(this.slotHeightPx * 2, 120)
      if (totalH <= viewH) {
        this.loadResourceList(false)
        return
      }
      if (scrollTop + viewH >= totalH - threshold) {
        this.loadResourceList(false)
      }
    },
    onDateChipTap(key) {
      this.selectedDateKey = key
    },
    applyListHeightFallback() {
      const contentH = this.getViewportContentHeight()
      const topFallback = Math.ceil(uni.upx2px(320))
      const bodyPad = Math.ceil(uni.upx2px(BS_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(BS_BODY_PAD_RPX))
      this.pageHeightPx = contentH

      uni.createSelectorQuery()
        .in(this)
        .select('.bs-top')
        .boundingClientRect((topRect) => {
          const topH =
            topRect && topRect.height > 0 ? Math.ceil(topRect.height) : Math.ceil(uni.upx2px(320))
          this.listHeightPx = Math.max(240, contentH - topH - bodyPad)
          this.$nextTick(() => {
            this.$refs.bsVList?.measureContainerHeight?.()
            this.tryAutoLoadMore()
          })
        })
        .exec()
    },
    onTabUpdate(idx) {
      this.tabIndex = typeof idx === 'number' ? idx : 0
    },
    titleFor(item) {
      return item.resourceName || this.$t('bookingServicePage.noUnit')
    },
    subFor(item) {
      if (item.affiliatedUnit) {
        return item.affiliatedUnit
      }
      if (item.kind === 'expert' && item.expertRating != null && item.expertRating !== '') {
        return `${this.$t('bookingServicePage.tabExpert')} · ${item.expertRating}`
      }
      return this.$t('bookingServicePage.noUnit')
    },
    introFor(item) {
      const intro = (item.introduction || '').trim()
      return intro || this.$t('bookingServicePage.noIntro')
    },
    avatarUrl(item) {
      const url = item && (item.photoFileUrl || (item.raw && item.raw.photoFileUrl))
      const result = url ? resolveResourceUrl(url) : ''
      return result
    },
    onBook(item) {
      if (!item || !item.id) return
      if (!ensureApiToken()) return
      saveBookingResourceCache(item.resourceType, item.id, item.raw || item)
      const path = BOOKING_PATHS[item.resourceType] || BOOKING_PATHS['004001']
      const q = [
        `id=${encodeURIComponent(item.id)}`,
        `resourceType=${encodeURIComponent(item.resourceType || this.currentResourceType)}`
      ].join('&')
      uni.navigateTo({ url: `${path}?${q}` })
    }
  }
}
</script>

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

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

.bs-top {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.border-top {
  border-top: 1rpx solid $morandi-border-soft;
}

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

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

.bs-date-scroll {
  width: 100%;
  white-space: nowrap;
}

.bs-date-row {
  display: inline-flex;
  flex-direction: row;
  align-items: stretch;
  gap: 12rpx;
  padding: 4rpx 8rpx 8rpx;
  min-width: min-content;
}

.bs-chip {
  flex-shrink: 0;
  min-width: 96rpx;
  padding: 12rpx 16rpx;
  border-radius: 12rpx;
  border: 1rpx solid #e5e7eb;
  background: #ffffff;
  box-sizing: border-box;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
}

.bs-chip--on {
  background: #22c55e;
  border-color: #16a34a;
}

.bs-chip__all {
  font-size: 26rpx;
  color: #111827;
  font-weight: 500;
}

.bs-chip--on .bs-chip__all {
  color: #ffffff;
  font-weight: 600;
}

.bs-chip__stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4rpx;
}

.bs-chip__name {
  font-size: 24rpx;
  color: #111827;
  font-weight: 500;
}

.bs-chip__date {
  font-size: 22rpx;
  color: #374151;
}

.bs-chip--on .bs-chip__name,
.bs-chip--on .bs-chip__date {
  color: #ffffff;
  font-weight: 600;
}

.bs-tabs {
  width: 100%;
}

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

.bs-tabs :deep(.u-tabs__wrapper__nav__line) {
  bottom: 1px;
}

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

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

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

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

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

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

.bs-item {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 16rpx;
  padding: 20rpx;
  box-sizing: border-box;
  background: #ffffff;
  border-radius: 12rpx;
  border: 1rpx solid $morandi-border-soft;
  min-width: 0;
}

.bs-item__row1 {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 16rpx;
  min-width: 0;
}

.bs-item__right {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6rpx;
}

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

.bs-item__sub {
  font-size: 24rpx;
  color: $morandi-text-secondary;
  line-height: 1.4;
  word-break: break-word;
}

.bs-item__intro {
  font-size: 22rpx;
  color: $morandi-text-muted;
  line-height: 1.45;
  word-break: break-word;
}

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

  .bs-chip__name {
    font-size: 22rpx;
    line-height: 1.65;
  }
}
</style>
