<script setup>
import { computed, nextTick, onMounted, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import Avatar from "@/components/Avatar.vue";
import {
  getMvpRankList,
  getMyMvpRank,
  getMyScoreRank,
  getMyTenRingRank,
  getScoreRankList,
  getTenRingRankList,
} from "@/apis";
import { capsuleHeight } from "@/util";
import useStore from "@/store";
import { storeToRefs } from "pinia";

const PAGE_SIZE = 10;

const store = useStore();
const { user } = storeToRefs(store);
const { getLvlName } = store;

const createRankState = () => ({
  list: [],
  page: 0,
  pageSize: PAGE_SIZE,
  loading: false,
  noMore: false,
  loaded: false,
  scrollTop: 0,
  myData: null,
  myDataLoaded: false,
});

const rankTabs = [
  {
    key: "score",
    title: "积分榜",
    subTitle: "排位赛积分",
    listApi: getScoreRankList,
    myApi: getMyScoreRank,
  },
  {
    key: "mvp",
    title: "MVP榜",
    subTitle: "MVP次数",
    listApi: getMvpRankList,
    myApi: getMyMvpRank,
  },
  {
    key: "tenRing",
    title: "十环榜",
    subTitle: "十环次数",
    listApi: getTenRingRankList,
    myApi: getMyTenRingRank,
  },
];

// 解析 ranking 页面传入的榜单参数，进入页面时默认选中对应 tab。
const getTabIndexByRouteParam = (tab) => {
  if (tab === "mvp") return 1;
  if (tab === "tenRing") return 2;
  return 0;
};

const rankStates = ref({
  score: createRankState(),
  mvp: createRankState(),
  tenRing: createRankState(),
});

const selectedIndex = ref(0);
const initialTabIndex = ref(0);
const pageMounted = ref(false);
const initializedFromRoute = ref(false);
const addBg = ref(false);
const currentScrollTop = ref(0);
const restoreScrollTop = ref(0);
const tabSwitchAnimating = ref(false);
const suppressScrollSync = ref(false);
const suppressLoadMore = ref(false);
const stickyTabsTop = capsuleHeight + 50;
const stickyTabsActive = ref(false);
const tabsStickyThreshold = ref(0);
const tabsStickyReady = ref(false);
const tabsHeight = ref(0);

const getTabConfig = (index = selectedIndex.value) => rankTabs[index];
const getTabKey = (index = selectedIndex.value) => getTabConfig(index).key;

// 统一提取榜单接口返回的列表数据，兼容数组和对象两种返回格式。
const getRankListFromResponse = (result) => {
  if (Array.isArray(result)) return result;
  if (Array.isArray(result?.list)) return result.list;
  if (Array.isArray(result?.items)) return result.items;
  return [];
};

// 为当前登录用户构造默认的个人榜单信息，避免接口未返回时底部区域缺数据。
const buildDefaultMyData = () => ({
  rank: null,
  userId: user.value.id,
  name: user.value.nickName,
  avatar: user.value.avatar,
  vip: user.value.vip,
  sVip: user.value.sVip,
  totalScore: 0,
  mvpCount: 0,
  tenRings: 0,
  totalGames: 0,
  totalCount: 0,
  rankName: user.value.lvlName,
  rankLvl: user.value.rankLvl,
});

const currentTabKey = computed(() => getTabKey(selectedIndex.value));
const currentState = computed(() => rankStates.value[currentTabKey.value]);
const currentList = computed(() => currentState.value.list);
const currentSubTitle = computed(() => getTabConfig(selectedIndex.value).subTitle);
const currentMyData = computed(() => {
  if (!user.value.id) return null;
  return currentState.value.myData || buildDefaultMyData();
});

// 统一格式化段位和场次文案，兼容不同接口的字段命名。
const formatLevelText = (item = {}) => {
  const levelName = item.rankName || getLvlName(item.rankLvl) || "暂无段位";
  const totalGames = item.totalGames ?? item.TotalGames ?? 0;
  return `${levelName}，${totalGames}场`;
};

// 统一读取榜单项的排名字段，没有后端 rank 时回退到前端序号。
const getDisplayRank = (item = {}, index = 0) => {
  return item.rank ?? index + 1;
};

// 底部个人排名在未上榜时展示占位符，而不是空白。
const getDisplayMyRank = (item = {}) => {
  return item.rank ?? "-";
};

const getScoreValue = (item = {}) => item.totalScore ?? 0;
const getMvpValue = (item = {}) => item.mvpCount ?? item.totalScore ?? 0;
const getTenRingValue = (item = {}) =>
  item.tenRings ?? item.TenRings ?? item.totalScore ?? 0;

// 根据当前选中的榜单类型，读取对应的展示值。
const getRankValue = (item = {}, index = selectedIndex.value) => {
  if (index === 0) return getScoreValue(item);
  if (index === 1) return getMvpValue(item);
  return getTenRingValue(item);
};

const getRankUnit = (index = selectedIndex.value) => {
  if (index === 0) return "分";
  return "次";
};

const isMember = (item = {}) => item.vip === true || item.sVip === true;

const getMemberNicknameClass = (item = {}) => [
  "rank-list-player-name",
  "member-nickname",
  item.vip === true && item.sVip !== true ? "member-nickname--vip" : "",
  item.sVip === true ? "member-nickname--svip" : "",
];

// 统一设置页面当前的视觉滚动状态，避免吸顶和顶部背景不同步。
const syncScrollVisualState = (scrollTop = 0) => {
  currentScrollTop.value = scrollTop;
  addBg.value = scrollTop > 100;
  if (!tabsStickyReady.value) {
    stickyTabsActive.value = false;
    return;
  }
  stickyTabsActive.value = scrollTop >= tabsStickyThreshold.value;
};

// 只保留一条滚动恢复链路：从当前滚动位置平滑滚到目标位置，避免多套控制同时生效造成闪烁。
const applyScrollPosition = async (
  fromScrollTop = currentScrollTop.value,
  toScrollTop = 0,
  withAnimation = false
) => {
  tabSwitchAnimating.value = withAnimation;
  restoreScrollTop.value = fromScrollTop;
  await nextTick();
  restoreScrollTop.value = toScrollTop;
  syncScrollVisualState(toScrollTop);
};

// 请求指定榜单的某一页数据，只有当前榜单会追加分页，不影响其他榜单的浏览状态。
const loadRankPage = async (tabKey, { reset = false } = {}) => {
  const state = rankStates.value[tabKey];
  const config = rankTabs.find((item) => item.key === tabKey);
  if (!config || state.loading) return;
  if (!reset && state.noMore) return;

  const nextPage = reset ? 1 : state.page + 1;
  state.loading = true;
  if (reset) state.noMore = false;

  try {
    const result = await config.listApi(undefined, nextPage, PAGE_SIZE);
    const list = getRankListFromResponse(result);
    state.list = reset ? list : state.list.concat(list);
    state.page = nextPage;
    state.loaded = true;
    state.noMore = list.length < PAGE_SIZE;
  } catch (error) {
    if (reset) {
      state.list = [];
      state.page = 0;
      state.loaded = false;
      state.noMore = false;
    }
    uni.showToast({
      title: "排行榜加载失败",
      icon: "none",
    });
    console.error("load rank page error", error);
  } finally {
    state.loading = false;
  }
};

// 每个榜单独立请求一次个人排名信息，切回该榜单时直接复用，避免打断浏览上下文。
const loadMyRankData = async (tabKey) => {
  if (!user.value.id) return;

  const state = rankStates.value[tabKey];
  const config = rankTabs.find((item) => item.key === tabKey);
  if (!config || state.myDataLoaded) return;

  try {
    const result = await config.myApi();
    state.myData = {
      ...buildDefaultMyData(),
      ...(result || {}),
    };
  } catch (error) {
    state.myData = buildDefaultMyData();
    console.error("load my rank data error", error);
  } finally {
    state.myDataLoaded = true;
  }
};

// 首次进入或切换到未加载过的榜单时，初始化它的分页数据和个人横条数据。
const ensureTabReady = async (index = selectedIndex.value) => {
  const tabKey = getTabKey(index);
  const state = rankStates.value[tabKey];

  if (!state.loaded) {
    await loadRankPage(tabKey, { reset: true });
  }

  if (user.value.id && !state.myDataLoaded) {
    await loadMyRankData(tabKey);
  }

  await nextTick();
  return state.scrollTop || 0;
};

onLoad((options = {}) => {
  initialTabIndex.value = getTabIndexByRouteParam(options.tab);
  selectedIndex.value = initialTabIndex.value;
  if (pageMounted.value && !initializedFromRoute.value) {
    initializePage();
  }
});

// 页面初始化同时兼容 onLoad 和 onMounted 的先后顺序，确保首屏一定落到路由指定的榜单。
const initializePage = async () => {
  if (initializedFromRoute.value) return;
  initializedFromRoute.value = true;
  const nextScrollTop = await ensureTabReady(selectedIndex.value);
  await applyScrollPosition(0, nextScrollTop, false);
  setTimeout(() => {
    measureTabsMetrics();
  }, 0);
};

onMounted(async () => {
  pageMounted.value = true;
  await initializePage();
});

// 切换榜单时保留原榜单的列表和滚动位置，切回来后继续从之前的位置浏览。
const handleSelect = async (index) => {
  if (index === selectedIndex.value) return;

  const previousTabKey = currentTabKey.value;
  rankStates.value[previousTabKey].scrollTop = currentScrollTop.value;
  const previousScrollTop = currentScrollTop.value;

  suppressScrollSync.value = true;
  suppressLoadMore.value = true;
  selectedIndex.value = index;
  const nextScrollTop = await ensureTabReady(index);
  await applyScrollPosition(previousScrollTop, nextScrollTop, false);
  setTimeout(() => {
    tabSwitchAnimating.value = false;
    suppressScrollSync.value = false;
    suppressLoadMore.value = false;
  }, 220);
};

// 触底后只加载当前榜单的下一页数据，其他榜单的数据和页码保持不变。
const loadMore = async () => {
  if (suppressLoadMore.value) return;
  await loadRankPage(currentTabKey.value);
};

// 实时记录当前榜单的滚动位置，切换回来时恢复到上一次浏览位置。
const onScrollView = (e) => {
  const scrollTop = e.detail.scrollTop || 0;
  if (suppressScrollSync.value) return;
  syncScrollVisualState(scrollTop);
  rankStates.value[currentTabKey.value].scrollTop = scrollTop;
};

// 计算 tab 在滚动内容中的真实位置和高度，作为吸顶切换的唯一依据。
const measureTabsMetrics = () => {
  const query = uni.createSelectorQuery();
  query
    .select("#rank-list-content-start")
    .boundingClientRect()
    .select(".rank-tabs-anchor")
    .boundingClientRect()
    .exec((res = []) => {
      const [startRect, rect] = res;
      if (!startRect || !rect) return;
      const tabOffset = rect.top - startRect.top;
      tabsStickyThreshold.value = Math.max(0, tabOffset - 92);
      tabsHeight.value = rect.height || 0;
      tabsStickyReady.value = true;
      syncScrollVisualState(currentScrollTop.value);
    });
};
</script>

<template>
  <view class="container">
    <view
      class="header"
      :style="{
        paddingTop: capsuleHeight + 'px',
      }"
    >
      <image
        :style="{ opacity: addBg ? 1 : 0 }"
        src="https://static.shelingxingqiu.com/shootmini/static/app-bg.png"
        mode="widthFix"
      />
      <navigator open-type="navigateBack">
        <image class="header-back" src="../static/back.png" mode="widthFix" />
      </navigator>
      <text :style="{ opacity: addBg ? 1 : 0 }">本赛季排行榜</text>
    </view>
    <scroll-view
      scroll-y
      :scroll-with-animation="tabSwitchAnimating"
      :scroll-top="restoreScrollTop"
      @scroll="onScrollView"
      @scrolltolower="loadMore"
      :style="{ height: user.id ? '90vh' : '100vh' }"
    >
      <view id="rank-list-content-start" class="content-start-anchor"></view>
      <image
        src="https://static.shelingxingqiu.com/attachment/2025-09-25/dd1p9b3wcrwnlnghiq.png"
        mode="widthFix"
        class="header-bg"
        @load="measureTabsMetrics"
      />
      <view
        v-if="stickyTabsActive"
        class="rank-tabs-placeholder"
        :style="{ height: `${tabsHeight}px` }"
      />
      <view
        class="rank-tabs rank-tabs-anchor"
        :class="{ 'rank-tabs-anchor-fixed': stickyTabsActive }"
        :style="stickyTabsActive ? { top: `${stickyTabsTop}px` } : {}"
      >
        <view
          v-for="(rankType, index) in rankTabs"
          :key="rankType.key"
          :style="{
            fontSize: index === selectedIndex ? '16px' : '14px',
            color: index === selectedIndex ? '#000' : '#fff',
            backgroundColor: index === selectedIndex ? '#FFD947' : '#383737',
          }"
          @tap="handleSelect(index)"
        >
          {{ rankType.title }}
        </view>
      </view>
      <view class="rank-list">
        <view class="rank-list-header">
          <text>排名</text>
          <text>用户ID</text>
          <text>{{ currentSubTitle }}</text>
        </view>
        <view
          v-for="(item, index) in currentList"
          :key="`${currentTabKey}-${index}-${item.userId || item.name}`"
          class="rank-list-item"
          :style="{
            backgroundColor: index % 2 === 0 ? '#9898981f' : 'transparent',
          }"
        >
          <image
            v-if="index === 0"
            class="player-bg"
            src="../static/melee-player-bg1.png"
            mode="aspectFill"
          />
          <image
            v-if="index === 1"
            class="player-bg"
            src="../static/melee-player-bg2.png"
            mode="aspectFill"
          />
          <image
            v-if="index === 2"
            class="player-bg"
            src="../static/melee-player-bg3.png"
            mode="aspectFill"
          />
          <image
            v-if="index === 0"
            class="player-crown"
            src="../static/champ1.png"
            mode="widthFix"
          />
          <image
            v-if="index === 1"
            class="player-crown"
            src="../static/champ2.png"
            mode="widthFix"
          />
          <image
            v-if="index === 2"
            class="player-crown"
            src="../static/champ3.png"
            mode="widthFix"
          />
          <view v-if="index > 2" class="view-crown">
            {{ getDisplayRank(item, index) }}
          </view>
          <Avatar :src="item.avatar" />
          <view class="rank-item-content">
            <view v-if="isMember(item)" :class="getMemberNicknameClass(item)">
              <text class="member-nickname__text">{{ item.name }}</text>
              <text v-if="item.sVip === true" class="member-nickname__shine">
                {{ item.name }}
              </text>
            </view>
            <text v-else class="rank-list-player-name truncate">{{ item.name }}</text>
            <text>{{ formatLevelText(item) }}</text>
          </view>
          <text class="rank-item-integral">
            <text
              :style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }"
            >
              {{ getRankValue(item) }}
            </text>
            {{ getRankUnit() }}
          </text>
        </view>
        <view
          v-if="currentState.loading && !currentList.length"
          class="no-data"
        >
          <text>加载中...</text>
        </view>
        <view
          v-else-if="!currentState.loading && !currentList.length"
          class="no-data"
        >
          <text>暂无数据</text>
        </view>
        <view v-else class="list-tip">
          <text v-if="currentState.loading">加载中...</text>
          <text v-else-if="currentState.noMore">没有更多了</text>
        </view>
      </view>
    </scroll-view>
    <view class="my-rank-data" v-if="currentMyData">
      <image
        src="https://static.shelingxingqiu.com/attachment/2025-08-05/dbuaf19pf7qd8ps0uh.png"
        mode="widthFix"
      />
      <text>{{ getDisplayMyRank(currentMyData) }}</text>
      <Avatar :src="currentMyData.avatar || user.avatar" />
      <view class="rank-item-content">
        <view
          v-if="isMember(currentMyData)"
          :class="getMemberNicknameClass(currentMyData)"
        >
          <text class="member-nickname__text">
            {{ currentMyData.name || user.nickName }}
          </text>
          <text
            v-if="currentMyData.sVip === true"
            class="member-nickname__shine"
          >
            {{ currentMyData.name || user.nickName }}
          </text>
        </view>
        <text v-else class="rank-list-player-name truncate">
          {{ currentMyData.name || user.nickName }}
        </text>
        <text>{{ formatLevelText(currentMyData) }}</text>
      </view>
      <text class="rank-item-integral">
        <text
          :style="{ fontSize: '14px', color: '#fff', marginRight: '5px' }"
        >
          {{ getRankValue(currentMyData) }}
        </text>
        {{ getRankUnit() }}
      </text>
    </view>
  </view>
</template>

<style scoped>
.container {
  width: 100%;
}

.content-start-anchor {
  width: 100%;
  height: 0;
}

.header-bg {
  width: 100%;
}

.header {
  width: 100%;
  height: 50px;
  display: flex;
  align-items: center;
  position: fixed;
  top: 0;
  z-index: 10;
  overflow: hidden;
}

.header-back {
  width: 22px;
  height: 22px;
  margin: 0px 15px;
  margin-top: 5px;
  position: relative;
}

.header > image:first-child {
  width: 100vw;
  height: 100vh;
  position: absolute;
  top: 0;
  left: 0;
  transition: all 0.5s ease;
}

.header > text {
  color: #fff;
  font-weight: bold;
  transition: all 0.5s ease;
  position: relative;
}

.rank-tabs {
  width: calc(100% - 20px);
  display: flex;
  justify-content: space-around;
  padding: 20rpx 10px;
}

.rank-tabs > view {
  width: 25%;
  padding: 10px;
  text-align: center;
  border-radius: 20px;
}

.rank-tabs-placeholder {
  width: 100%;
}

.rank-tabs-anchor-fixed {
  position: fixed;
  left: 0;
  z-index: 11;
  background: #000000;
  backdrop-filter: blur(8px);
}

.rank-list {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: calc(100% - 20px);
  color: #fff9;
  font-size: 12px;
  margin: 0 10px 10px 10px;
  border: 1px solid rgb(255 217 71 / 0.2);
  border-radius: 10px;
  background-color: #313131;
}

.rank-list > view {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  overflow: hidden;
  position: relative;
}

.rank-list-header {
  width: calc(100% - 20px) !important;
  padding: 10px;
}

.rank-list-header > text:nth-child(2) {
  width: 14%;
}

.rank-list-header > text:last-child {
  width: 30%;
  text-align: right;
}

.rank-list-item {
  padding: 10px 0;
}

.player-bg {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}

.player-crown {
  position: relative;
  width: 27px;
  height: 27px;
  margin: 0 15px;
}

.view-crown {
  width: 27px;
  height: 27px;
  line-height: 27px;
  text-align: center;
  border-radius: 50%;
  margin: 0 15px;
  color: #fff;
  background-color: #676767;
  position: relative;
}

.rank-item-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  width: 50%;
  position: relative;
  padding-left: 10px;
}

.rank-item-content > text:first-child {
  color: #fff;
  font-size: 14px;
  margin-bottom: 3px;
  width: 120px;
}

.rank-list-player-name {
  color: #fff;
  font-size: 14px;
  margin-bottom: 3px;
  width: 120px;
}

.rank-list-player-name .member-nickname__text,
.rank-list-player-name .member-nickname__shine {
  font-size: 14px;
}

.rank-list-item > text:last-child {
  margin-right: 10px;
  width: 56px;
  text-align: right;
}

.my-rank-data {
  width: calc(100% - 30px);
  padding: 15px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: #fff9;
  font-size: 12px;
  height: calc(10vh - 30px);
  position: relative;
  overflow: hidden;
  border-radius: 10px;
}

.my-rank-data > image:first-child {
  position: absolute;
  width: 100%;
  left: 0;
  top: -5px;
}

.my-rank-data > text:nth-child(2) {
  background-color: #c1a434;
  position: relative;
  color: #fff;
  padding: 3px 5px;
  border-radius: 20px;
  margin: 10px;
  font-size: 14px;
  min-width: 15px;
  text-align: center;
}

.my-rank-data > text:last-child {
  position: relative;
  margin-right: 10px;
  width: 65px;
  text-align: right;
}

.my-rank-data > .rank-item-content > text:first-child {
  color: #fed847;
}

.my-rank-data > .rank-item-integral {
  color: #fff9;
  font-size: 12px;
  margin-right: 0 !important;
}

.no-data {
  width: 100%;
  height: 400px;
  display: flex;
  justify-content: center !important;
  align-items: center;
  color: #fff9;
  font-size: 14px;
}

.list-tip {
  justify-content: center !important;
  color: #fff9;
  font-size: 12px;
  min-height: 60rpx;
}
</style>
