<template>
  <div class="base-list" ref="listRef">
    <div v-if="loading && list.length === 0" class="loading-wrap">
      <van-loading size="24px">加载中...</van-loading>
    </div>
    <template v-for="(item, index) in list" :key="item.id">
      <slot :item="item" :index="index" />
    </template>
    <div v-if="loading && list.length > 0" class="loading-wrap">
      <van-loading size="24px">加载中...</van-loading>
    </div>
    <div v-if="finished && list.length > 0" class="finished-text">{{ finishedText }}</div>
    <div v-if="finished && list.length === 0 && !loading" class="empty-text">暂无数据</div>
    <div ref="sentinel" class="sentinel"></div>
  </div>
</template>

<script>
export default {
  name: 'BaseList',
  props: {
    url: { type: String, required: true },
    method: { type: String, default: 'get' },
    params: { type: Object, default: () => ({}) },
    pageSize: { type: Number, default: 10 },
    finishedText: { type: String, default: '没有更多了' },
    parseData: { type: Function, default: (res) => res.data.items },
    cacheKey: { type: String, default: '' },
  },
  data() {
    return {
      loading: false,
      finished: false,
      list: [],
      page: 1,
      requestId: 0,
      observer: null,
      _cacheKey: null,
      _dataCache: null,
    }
  },
  emits: ['update:list', 'load', 'refresh'],
  expose: ['refresh'],
  watch: {
    url() {
      this.refresh()
    },
    params: {
      handler() {
        if (this._skipWatch) return
        this.refresh()
      },
      deep: true
    }
  },
  created() {
    if (this.cacheKey) {
      const cached = sessionStorage.getItem(this.cacheKey)
      if (cached) {
        try {
          const { list, page, finished, params } = JSON.parse(cached)
          if (JSON.stringify(this.params) === JSON.stringify(params)) {
            this._dataCache = { list, page, finished }
          }
        } catch (e) { }
      }
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && !this.loading && !this.finished) {
          this.loadMore()
        }
      }, { rootMargin: '100px' })
      if (this.$refs.sentinel) {
        this.observer.observe(this.$refs.sentinel)
      }
    })
    window.addEventListener('scroll', this.onWindowScroll)
  },
  beforeUnmount() {
    this.observer?.disconnect()
    window.removeEventListener('scroll', this.onWindowScroll)
    if (this.cacheKey && this.list.length > 0) {
      sessionStorage.setItem(this.cacheKey, JSON.stringify({
        list: this.list,
        page: this.page,
        finished: this.finished,
        params: this.params
      }))
    }
  },
  methods: {
    onWindowScroll() {
      if (this.finished || this.loading) return
      const el = this.$refs.sentinel
      if (!el) return
      const rect = el.getBoundingClientRect()
      if (rect.top < window.innerHeight) {
        this.loadMore()
      }
    },
    loadMore() {
      if (this.loading) return
      if (this._dataCache && this._dataCache.params && JSON.stringify(this.params) === JSON.stringify(this._dataCache.params)) {
        this.list = this._dataCache.list
        this.page = this._dataCache.page
        this.finished = this._dataCache.finished
        this.loading = false
        return
      }
      const currentRequestId = this.requestId
      this.loading = true
      const request = this.method === 'get' ? this.$get : this.$post
      request(this.url, { page: this.page, pageSize: this.pageSize, ...this.params })
        .then(res => {
          if (currentRequestId !== this.requestId) return
          const data = this.parseData(res)
          // console.log(data);
          const safeData = data || []

          if (this.page === 1) {
            this.list = safeData
          } else {
            this.list.push(...safeData)
          }
          this.page++
          this.finished = safeData.length < this.pageSize
          this.$emit('update:list', this.list)
          this.$emit('load', this.list)
        })
        .catch(err => {
          if (currentRequestId !== this.requestId) return
          this.$showFailToast(err.message || '加载失败')
        })
        .finally(() => { this.loading = false })
    },
    refresh() {
      this.requestId++
      this.list = []
      this.page = 1
      this.finished = false
      this.loading = false
      this._skipWatch = true
      this.loadMore()
      this.$emit('refresh', this.list)
      this._skipWatch = false
    },
  },
}
</script>

<style scoped>
.base-list {
  min-height: 200px;
  display: flex;
  flex-direction: column;
}

.loading-wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px;
  width: 100%;
  box-sizing: border-box;
}

.loading-wrap :deep(.van-loading) {
  display: inline-flex;
  align-items: center;
  flex-direction: row;
  white-space: nowrap;
  float: none !important;
}

.loading-wrap :deep(.van-loading__text) {
  float: none !important;
}

.finished-text,
.empty-text {
  text-align: center;
  padding: 15px;
  color: #969799;
  font-size: 14px;
}
</style>