<template>
  <div class="pc-sports-list-container">
    <div class="list-wrapper layout-column">

      <div class="search-segment-bar" v-if="false">
        <el-scrollbar>
          <div class="sport-segment-container">
            <div
                class="sport-segment-item"
                v-for="(item, index) in sportCategoryList"
                :key="index"
                :class="{ 'is-active': currentSportIndex === index }"
                @click="onSportCategoryChange(index)"
            >
              <span class="sport-icon">{{ item.icon }}</span>
              <span class="sport-name">{{ t(item.name) }}</span>
            </div>
          </div>
        </el-scrollbar>
      </div>

      <div class="top-filter-bar">
        <div class="top-tabs">
          <div
              v-for="tab in tabList"
              :key="tab.value"
              class="top-tab-item"
              :class="{ 'is-active': currentState === tab.value }"
              @click="onTabChange(tab.value)"
          >
            {{ tab.name }}
          </div>
        </div>

        <div class="top-actions">
          <div class="search-box">
            <el-input
                v-model="keyword"
                :placeholder="t('搜索赛事关键字')"
                clearable
                @keyup.enter="onSearch"
                @clear="onSearch"
                class="search-input"
            >
              <template #append>
                <el-button @click="onSearch">
                  <el-icon><Search /></el-icon>
                </el-button>
              </template>
            </el-input>
          </div>
          <el-button plain @click="onRefresh" :title="t('刷新数据')">
            <el-icon class="mr-1"><RefreshRight /></el-icon>
            <span>{{ t('刷新') }}</span>
          </el-button>

          <el-button
              link
              type="primary"
              @click="router.push({name: 'AllHistory', query: { tab: 1 }})"
          >
            <el-icon class="mr-1"><DataBoard /></el-icon>
            <span>{{ t('投注历史') }}</span>
          </el-button>
        </div>
      </div>

      <div class="right-content" ref="rightContentRef">
        <SportDateFilter :state="currentState" v-model:date="currentDate" @change="dateChange" v-show="currentState !== 1" />
        
        <div class="match-list-body">
          <el-scrollbar height="100%" ref="scrollbarRef" @scroll="onScroll" @end-reached="onLoadMore">
            <el-skeleton :loading="skeletonLoading" animated>
              <template #template>
                <div class="match-card" v-for="i in 5" :key="i">
                  <div class="match-card-header">
                    <div class="league-info">
                      <div class="decorate-bar"></div>
                      <el-skeleton-item variant="circle" style="width: 16px; height: 16px; margin-right: 6px;" />
                      <el-skeleton-item variant="text" style="width: 80px;" />
                    </div>
                    <div class="match-status-time">
                      <el-skeleton-item variant="text" style="width: 40px; margin-right: 8px;" />
                      <el-skeleton-item variant="text" style="width: 100px;" />
                    </div>
                  </div>

                  <div class="match-card-main">
                    <div class="match-teams">
                      <div class="team-row">
                        <div class="team-info">
                          <el-skeleton-item variant="circle" style="width: 18px; height: 18px;" />
                          <el-skeleton-item variant="text" style="width: 60px; margin-left: 6px;" />
                        </div>
                        <el-skeleton-item variant="text" style="width: 20px;" />
                      </div>
                      <div class="team-row mt-3">
                        <div class="team-info">
                          <el-skeleton-item variant="circle" style="width: 18px; height: 18px;" />
                          <el-skeleton-item variant="text" style="width: 80px; margin-left: 6px;" />
                        </div>
                        <el-skeleton-item variant="text" style="width: 20px;" />
                      </div>
                      <span class="time-text"></span>
                    </div>

                    <div class="match-odds-grids">
                      <div class="market-col" v-for="j in 3" :key="j">
                        <div style="display: flex; justify-content: center;">
                          <el-skeleton-item variant="text" style="width: 60px; margin-bottom: 4px;" />
                        </div>
                        <div class="market-values">
                          <el-skeleton-item variant="button" class="odds-btn" style="border: none;" v-for="k in 3" :key="k" />
                        </div>
                      </div>
                    </div>

                    <div class="match-more-toggle">
                      <el-skeleton-item variant="text" style="width: 30px;" />
                    </div>
                  </div>
                </div>
              </template>

              <template #default>
                <div :style="{ paddingTop: listPaddingTop + 'px', paddingBottom: listPaddingBottom + 'px' }">
                  <div class="match-card" v-for="item in visibleSportList" :key="item.data_id || item.id">

                    <div class="match-card-header">
                      <div class="league-info">
                        <div class="decorate-bar"></div>
                        <el-avatar v-if="item.league_logo" :src="item.league_logo" :size="16" style="margin-right: 6px; background-color: transparent;"></el-avatar>
                        <span class="league-name" :title="locale === 'zh' ? item.league : item.league_en">{{ locale === 'zh' ? item.league : item.league_en }}</span>
                        <el-tag style="margin-left: 10px" size="small" v-if="item.state === 1 && item.fixture_status">
                          {{ item.fixture_status?.long }}
                        </el-tag>
                        <el-tag v-if="item.is_roll === 1" style="margin-left: 10px">{{ t('滚球') }}</el-tag>
                        <span v-if="item.state === 1" style="font-size: 12px;color: #999;margin-left: 10px">
                          <template v-if="item.fixture_status">
                            {{ item.fixture_status?.seconds }}
                          </template>
                        </span>
                      </div>
                      <div class="match-status-time">
                        <span style="font-size: 13px;color: #999">
                          {{ item.game_time || '--:--' }}
                        </span>
                      </div>
                    </div>

                    <div class="match-card-main" @click="goDetail(item)">
                      <div class="match-teams">
                        <div class="team-row">
                          <div style="display: flex;align-items: center;gap: 6px;">
                            <div class="team-info">
                              <el-avatar :src="item.home_team_logo" :size="24" class="team-logo"></el-avatar>
                              <span class="team-name" :title="locale === 'zh' ? item.home_team : item.home_team_en">
                                {{ locale === 'zh' ? item.home_team : item.home_team_en }}
                              </span>
                            </div>
                            <el-tag size="small" type="success">{{ t('主') }}</el-tag>
                          </div>
                          <span class="team-score" v-if="item.state !== 0" :class="{'text-live': item.state === 1}">
                            {{ getScore(item.score, 0) }}
                          </span>
                        </div>

                        <div class="team-row mt-3">
                          <div style="display: flex;align-items: center;gap: 6px;">
                            <div class="team-info">
                              <el-avatar :src="item.guest_team_logo" :size="24" class="team-logo"></el-avatar>
                              <span class="team-name" :title="locale === 'zh' ? item.guest_team : item.guest_team_en">{{ locale === 'zh' ? item.guest_team : item.guest_team_en }}</span>
                            </div>
                            <el-tag size="small" type="danger">{{ t('客') }}</el-tag>
                          </div>
                          <span class="team-score" v-if="item.state !== 0" :class="{'text-live': item.state === 1}">
                            {{ getScore(item.score, 1) }}
                          </span>
                        </div>
                      </div>

                      <div class="match-odds-grids">
                        <template v-if="[0, 1].includes(item.state) && item.odds && item.odds.length > 0">
                          <div class="market-col" v-for="market in item.odds.slice(0, 3)" :key="market.id">
                            <div class="market-name" :title="locale === 'zh' ? market.name : market.name_en">{{ locale === 'zh' ? market.name : market.name_en }}</div>

                            <div class="market-values">
                              <div
                                  class="odds-btn"
                                  v-for="(opt, optIndex) in (market.values || []).slice(0, 3)"
                                  :key="optIndex"
                                  :class="{
                                    'is-locked': item.is_locked || opt.suspended || !opt.odd || market.is_findLocked,
                                    'is-active': isSelected(item.id, market.id, opt),
                                    'odds-up': isOddsUp(getOddsKey(item.id, market.id, opt)),
                                    'odds-down': isOddsDown(getOddsKey(item.id, market.id, opt))
                                  }"
                                  @click.stop="handleOddsClick(item, market, opt)"
                              >
                                <div class="odds-name-wrapper">
                                  <span class="selection-name" style="margin-right: 4px" :title="locale === 'zh' ? opt.value_text : opt.value">
                                    {{ translateSelection(locale === 'zh' ? opt.value_text : opt.value) }}
                                  </span>
                                  <span class="handicap-val" v-if="opt.handicap_text">{{ opt.handicap_text }}</span>
                                </div>

                                <div class="odds-val-wrapper">
                                  <el-icon v-if="!opt.odd || opt.suspended || item.is_locked || market.is_findLocked " class="lock-icon"><Lock /></el-icon>
                                  <span v-else class="odds-val">
                                    <span>{{ opt.odd || opt.odd === 0 ? opt.odd : '-' }}</span>
                                    <span v-if="showChangeIcon(getOddsKey(item.id, market.id, opt))"
                                          class="change-icon"
                                          :class="isOddsUp(getOddsKey(item.id, market.id, opt)) ? 'up' : 'down'">
                                       <el-icon v-show="isOddsUp(getOddsKey(item.id, market.id, opt))"><CaretTop /></el-icon>
                                      <el-icon v-show="!isOddsUp(getOddsKey(item.id, market.id, opt))"><CaretBottom /></el-icon>
                                    </span>
                                  </span>
                                </div>
                              </div>
                            </div>
                          </div>
                        </template>

                        <div class="empty-odds-tip" v-else>
                          <span class="empty-text" v-if="item.state !== 2">{{ t('暂无盘口数据') }}</span>
                          <span class="empty-text" v-else>{{ t('已完场') }}</span>
                        </div>
                      </div>

                      <div class="match-more-toggle" @click.stop="toggleMatchExpand(item.id)" v-if="item.state !== 2">
                        <div class="more-content" v-if="item.odds_count > 0">
                          <span class="more-text">+{{ item.odds_count }} {{ t('玩法') }}</span>
                          <el-icon class="toggle-icon"><ArrowDown v-if="!isMatchExpanded(item.id)" /><ArrowUp v-else /></el-icon>
                        </div>
                        <span v-else class="empty-more">--</span>
                      </div>
                    </div>

                    <el-collapse-transition>
                      <div class="match-expanded-wrapper" v-show="isMatchExpanded(item.id)">
                        <div class="match-expanded-area">
                          <div class="expanded-market-item" v-for="market in item.odds" :key="market.id">
                            <div class="expanded-market-header">
                              <div class="header-left">
                                <div class="m-decorate"></div>
                                <span class="m-name">{{ locale === 'zh' ? market.name : market.name_en }}</span>
                              </div>
                              <el-button
                                  v-if="market.values && market.values.length > currentCols"
                                  link
                                  type="primary"
                                  @click="toggleMarketExpand(item.id, market.id)"
                              >
                                {{ isMarketExpanded(item.id, market.id) ? t('收起') : t('展开全部') }}
                                <el-icon class="ml-1"><ArrowDown v-if="!isMarketExpanded(item.id, market.id)" /><ArrowUp v-else /></el-icon>
                              </el-button>
                            </div>

                            <div
                                class="m-values-grid"
                                :style="{ gridTemplateColumns: `repeat(${currentCols}, minmax(0, 1fr))` }"
                            >
                              <div
                                  class="odds-btn"
                                  v-for="(opt, optIndex) in getExpandedMarketValues(item.id, market)"
                                  :key="optIndex"
                                  :class="{
                                    'is-locked': item.is_locked || opt.suspended || !opt.odd || market.is_findLocked,
                                    'is-active': isSelected(item.id, market.id, opt),
                                    'odds-up': isOddsUp(getOddsKey(item.id, market.id, opt)),
                                    'odds-down': isOddsDown(getOddsKey(item.id, market.id, opt))
                                  }"
                                  @click.stop="handleOddsClick(item, market, opt)"
                              >
                                <div class="odds-name-wrapper">
                                  <span class="selection-name" style="margin-right: 4px" :title="locale === 'zh' ? opt.value_text : opt.value">
                                    {{ translateSelection(locale === 'zh' ? opt.value_text : opt.value) }}
                                  </span>
                                  <span class="handicap-val" v-if="opt.handicap_text">{{ opt.handicap_text || '-' }}</span>
                                </div>
                                <div class="odds-val-wrapper">
                                  <el-icon v-if="!opt.odd || opt.suspended || item.is_locked || market.is_findLocked" class="lock-icon"><Lock /></el-icon>
                                  <span v-else class="odds-val">
                                    {{ opt.odd || opt.odd === 0 ? opt.odd : '-' }}
                                    <span
                                        v-if="showChangeIcon(getOddsKey(item.id, market.id, opt))"
                                          class="change-icon"
                                          :class="isOddsUp(getOddsKey(item.id, market.id, opt)) ? 'up' : 'down'">
                                      <el-icon v-show="isOddsUp(getOddsKey(item.id, market.id, opt))"><CaretTop /></el-icon>
                                      <el-icon v-show="!isOddsUp(getOddsKey(item.id, market.id, opt))"><CaretBottom /></el-icon>
                                    </span>
                                  </span>
                                </div>
                              </div>
                            </div>
                          </div>
                        </div>
                      </div>
                    </el-collapse-transition>
                  </div>

                  <el-empty v-if="sportList.length === 0" :description="t('暂无赛事数据')" />
                </div>
                
                <div class="loading-more" v-if="sportList.length > 0">
                  <text>{{ isNoMore ? t('没有更多了') : t('正在加载') }}</text>
                </div>
              </template>
            </el-skeleton>
          </el-scrollbar>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import SportDateFilter from "@/views/SportsBetting/SportDateFilter.vue";

defineOptions({
  name: 'SportsBetting'
})

import { ref, watch, computed, onMounted, onActivated, onUnmounted, nextTick } from "vue"
import { useRouter } from "vue-router"
import { useI18n } from "vue-i18n"
import { getSportSchedule } from "@/request/api.js"
import { useOrderStore } from "@/store/bettingSheet.js"
// 引入搜索图标 Search
import { RefreshRight, Lock, ArrowDown, ArrowUp, DataBoard, CaretBottom, CaretTop, Search } from '@element-plus/icons-vue'
import socket from '@/utils/websocket.js'

const { t, locale } = useI18n()
const router = useRouter()
const orderStore = useOrderStore()

const skeletonLoading = ref(true)
const sportList = ref([])
const concurrency = ref(false)

const isNoMore = ref(false)
const isFetching = ref(false)
const currentPage = ref(1)
const currentLimit = ref(20)
const scrollbarRef = ref(null)

// === 新增：搜索与分类状态 ===
const keyword = ref('')
const currentSportIndex = ref(0)
const sportCategoryList = ref([
  { name: '足球', icon: '⚽️', value: 1 },
  { name: '篮球', icon: '🏀', value: 2 },
  { name: '网球', icon: '🎾', value: 3 },
  { name: '排球', icon: '🏐', value: 4 },
  { name: '羽毛球', icon: '🏸', value: 5 },
  { name: '乒乓球', icon: '🏓', value: 6 },
  { name: '电竞', icon: '🎮', value: 7 }
])

const onSearch = () => {
  onQuery(1, currentLimit.value)
}

const onSportCategoryChange = (index) => {
  currentSportIndex.value = index
  // 暂只做样式展示，不触发接口查询
  console.log('选择的体育分类:', sportCategoryList.value[index].name)
}
// === 新增结束 ===

const tabList = computed(() => [
  { name: t('进行中'), value: 1 },
  { name: t('未开始'), value: 0 },
  { name: t('已完场'), value: 2 },
])

const currentState = ref(1)
const currentDate = ref('')
const selectedMap = ref({})

// === 核心边界配置 ===
const MIN_NEW_ITEMS = 10;
const TIME_INCREMENT_MS = 60000;
let currentQueryId = 0;

// ====================== 虚拟滚动核心逻辑 ======================
const scrollTop = ref(0);
const itemHeight = 100; // 预估PC单卡片的高度
const containerHeight = ref(800); 

const onScroll = ({ scrollTop: top }) => {
  scrollTop.value = top;
};

// 为PC端提供更宽裕的缓冲（上下多渲染几条防白屏抖动）
const startIndex = computed(() => {
  let index = Math.floor(scrollTop.value / itemHeight) - 4;
  return Math.max(0, index);
});

const endIndex = computed(() => {
  let count = Math.ceil(containerHeight.value / itemHeight) + 15;
  return Math.min(sportList.value.length, startIndex.value + count);
});

const visibleSportList = computed(() => {
  return sportList.value.slice(startIndex.value, endIndex.value);
});

const listPaddingTop = computed(() => {
  return startIndex.value * itemHeight;
});

const listPaddingBottom = computed(() => {
  return (sportList.value.length - endIndex.value) * itemHeight;
});
// ==============================================================

// ====================== 新增：赔率变化闪烁逻辑 ======================
const oddsChangeMap = ref(new Map()) 

const getOddsKey = (matchId, marketId, opt) => {
  if (opt.handicap !== null && opt.handicap !== undefined && opt.handicap !== '') {
    return `${matchId}_${marketId}_${opt.value}_${opt.handicap}`
  }
  return `${matchId}_${marketId}_${opt.value}`
}

const isOddsUp = (key) => {
  const change = oddsChangeMap.value.get(key)
  return change?.direction === 'up' && (Date.now() - change.timestamp < 3000)
}

const isOddsDown = (key) => {
  const change = oddsChangeMap.value.get(key)
  return change?.direction === 'down' && (Date.now() - change.timestamp < 3000)
}

const showChangeIcon = (key) => {
  const change = oddsChangeMap.value.get(key)
  return !!change && (Date.now() - change.timestamp < 3000)
}

// ==========================================
// ====== 宽度监听与动态列数适配逻辑 ==========
// ==========================================
const rightContentRef = ref(null)
const currentCols = ref(4)
let resizeObserver = null

const updateColsByWidth = (width) => {
  if (width >= 1600) currentCols.value = 12
  else if (width >= 1400) currentCols.value = 10
  else if (width >= 1200) currentCols.value = 8
  else if (width >= 1000) currentCols.value = 6
  else if (width >= 800) currentCols.value = 5
  else if (width >= 600) currentCols.value = 4
  else currentCols.value = 3
}

// ========== WebSocket 实时刷新（去除静默，改为本地排序插入） ============
const handleSocketMessage = (e) => {
  const res = e.detail;
  if (!res || res.type !== 'sport_list' || !Array.isArray(res.message)) return;

  res.message.forEach((newMatch) => {
    const idx = sportList.value.findIndex(item => item.data_id === newMatch.data_id);

    if (idx !== -1) {
      const oldMatch = sportList.value[idx];

      // 检测赔率变化并记录闪烁
      if (newMatch.odds && Array.isArray(newMatch.odds) && oldMatch.odds && Array.isArray(oldMatch.odds)) {
        newMatch.odds.forEach(newMarket => {
          const oldMarket = oldMatch.odds.find(m => m.id === newMarket.id);
          if (oldMarket && oldMarket.values && newMarket.values) {
            newMarket.values.forEach(newOpt => {
              const oldOpt = oldMarket.values.find(o =>
                  o.value === newOpt.value &&
                  (o.handicap === newOpt.handicap || (!o.handicap && !newOpt.handicap))
              );

              if (oldOpt && oldOpt.odd && newOpt.odd) {
                const oldOddNum = parseFloat(oldOpt.odd);
                const newOddNum = parseFloat(newOpt.odd);

                if (!isNaN(oldOddNum) && !isNaN(newOddNum) && oldOddNum !== newOddNum) {
                  const key = getOddsKey(oldMatch.id, newMarket.id, newOpt);
                  const direction = newOddNum > oldOddNum ? 'up' : 'down';

                  oddsChangeMap.value.set(key, {
                    direction,
                    timestamp: Date.now()
                  });

                  let timer = setTimeout(() => {
                    oddsChangeMap.value.delete(key);
                    clearTimeout(timer);
                  }, 3100);
                }
              }
            });
          }
        });
      }

      const isStateChanged = newMatch.state !== undefined && Number(newMatch.state) !== Number(currentState.value);
      const isStatusOffline = newMatch.status !== undefined && Number(newMatch.status) === 0;

      if (isStateChanged || isStatusOffline) {
        sportList.value.splice(idx, 1);
      } else {
        newMatch.odds.forEach(jtem => {
          if (newMatch.odd_ids_locked && Array.isArray(newMatch.odd_ids_locked)) {
            if (newMatch.odd_ids_locked.find(trim => jtem.id === trim)) {
              jtem.is_findLocked = true;
            }
          }
        });

        sportList.value[idx] = { ...sportList.value[idx], ...newMatch };
        if (typeof orderStore.updateMatchStatus === 'function') {
          orderStore.updateMatchStatus(newMatch.data_id, newMatch);
        }
      }
    } else {
      // 本地不存在的数据，直接做插入和本地排序，摒弃接口拉取
      const isStateMatching = newMatch.state !== undefined && Number(newMatch.state) === Number(currentState.value);
      const isStatusOnline = newMatch.status === undefined || Number(newMatch.status) === 1;

      if (isStateMatching && isStatusOnline) {
        let matchToInsert = { ...newMatch };
        if (typeof matchToInsert.odds === 'string') {
          try { matchToInsert.odds = JSON.parse(matchToInsert.odds); } catch (e) { matchToInsert.odds = []; }
        }

        if (matchToInsert.odds && Array.isArray(matchToInsert.odds)) {
          matchToInsert.odds.forEach(jtem => {
            if (matchToInsert.odd_ids_locked && Array.isArray(matchToInsert.odd_ids_locked)) {
              if (matchToInsert.odd_ids_locked.find(trim => jtem.id === trim)) {
                jtem.is_findLocked = true;
              }
            }
          });
        }

        sportList.value.push(matchToInsert);

        sportList.value.sort((a, b) => {
          const timeA = a.game_time ? new Date(a.game_time.replace(/-/g, '/')).getTime() : 0;
          const timeB = b.game_time ? new Date(b.game_time.replace(/-/g, '/')).getTime() : 0;

          if (currentState.value === 2) {
            return timeB - timeA;
          } else {
            return timeA - timeB;
          }
        });
      }
    }
  });

  nextTick(); 
};

onMounted(() => {
  syncFromStore();
  onQuery(1, currentLimit.value);
  
  socket.eventBus.addEventListener('onSocketMessage', handleSocketMessage);

  if (rightContentRef.value) {
    resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) {
        updateColsByWidth(entry.contentRect.width);
      }
    });
    resizeObserver.observe(rightContentRef.value);
  }

  nextTick(() => {
    const scrollDom = document.querySelector('.scroll-view-container');
    if (scrollDom) {
      containerHeight.value = scrollDom.clientHeight || 800;
    }
  });
});

onActivated(() => {
  syncFromStore();
});

onUnmounted(() => {
  socket.eventBus.removeEventListener('onSocketMessage', handleSocketMessage);

  if (resizeObserver) {
    resizeObserver.disconnect();
    resizeObserver = null;
  }
});

const onRefresh = () => {
  onQuery(1, currentLimit.value);
};

const onLoadMore = () => {
  if (isNoMore.value || isFetching.value || skeletonLoading.value) return;
  onQuery(currentPage.value + 1, currentLimit.value);
};

// ==========================================
// ========== 调度与请求核心架构 =============
// ==========================================
async function onQuery(pageNo, pageSize) {
  const queryId = ++currentQueryId;

  try {
    if (pageNo === 1) {
      isNoMore.value = false;
      scrollTop.value = 0;
      currentPage.value = 1;
      
      // 重置滚动条回到顶部
      if (scrollbarRef.value) {
        scrollbarRef.value.setScrollTop(0);
      }
      
      skeletonLoading.value = true;
      sportList.value = []; 
    } else {
      if (isFetching.value || isNoMore.value) return;
    }

    isFetching.value = true;

    let accumulatedNewItems = [];
    let currentApiGameTime = null;
    let attemptCount = 0;
    const maxAttempts = 5;

    if (pageNo > 1 && sportList.value.length > 0) {
      const lastItem = sportList.value[sportList.value.length - 1];
      if (lastItem && lastItem.game_time) {
        currentApiGameTime = lastItem.game_time;
      }
    }

    let targetCount = pageNo === 1 ? pageSize : MIN_NEW_ITEMS;

    while (accumulatedNewItems.length < targetCount && !isNoMore.value && attemptCount < maxAttempts) {
      if (queryId !== currentQueryId) return;

      const params = {
        page: 1, 
        limit: pageSize,
        state: currentState.value,
        from: 'pc'
      };

      // 新增：携带搜索关键字
      if (keyword.value) {
        params.keyword = keyword.value;
      }

      if (currentApiGameTime) {
        params.game_time = currentApiGameTime;
      }
      
      if (currentState.value !== 1) {
        params.start_time = currentDate.value;
        params.end_time = currentDate.value;
      }
      
      const res = await getSportSchedule(params);
      
      if (queryId !== currentQueryId) return;

      if (res && res.code === 1 && res.data && res.data.list && res.data.list.length > 0) {
        const rawList = res.data.list;

        const formatList = rawList.map(item => {
          if (typeof item.odds === 'string') {
            try { item.odds = JSON.parse(item.odds); } catch(e) { item.odds = []; }
          }
          if (item.odds && Array.isArray(item.odds)) {
            item.odds.forEach(jtem => {
              if (item.odd_ids_locked && Array.isArray(item.odd_ids_locked) && item.odd_ids_locked.find(trim => jtem.id === trim)) {
                jtem.is_findLocked = true;
              }
            });
          }
          return item;
        });

        // 取出本视图上存在的，以及本次循环提取出的ID，严防重复穿透
        const existingIds = new Set([
          ...sportList.value.map(i => i.id),
          ...accumulatedNewItems.map(i => i.id)
        ]);

        const newItems = formatList.filter(item => !existingIds.has(item.id));
        accumulatedNewItems = accumulatedNewItems.concat(newItems);

        // 如果获取的底量不够limit，库里彻底干了
        if (rawList.length < pageSize) {
          isNoMore.value = true; 
        } else {
          // 被全部去重了，往外垫时间继续探
          if (newItems.length === 0) {
            let baseTimeStr = currentApiGameTime || rawList[rawList.length - 1].game_time;
            if (baseTimeStr) {
              let dateVal = new Date(baseTimeStr.replace(/-/g, '/'));
              if (!isNaN(dateVal.getTime())) {
                dateVal = new Date(dateVal.getTime() + TIME_INCREMENT_MS);
                const y = dateVal.getFullYear();
                const m = String(dateVal.getMonth() + 1).padStart(2, '0');
                const d = String(dateVal.getDate()).padStart(2, '0');
                const h = String(dateVal.getHours()).padStart(2, '0');
                const min = String(dateVal.getMinutes()).padStart(2, '0');
                const s = String(dateVal.getSeconds()).padStart(2, '0');
                currentApiGameTime = `${y}-${m}-${d} ${h}:${min}:${s}`;
              } else {
                currentApiGameTime = rawList[rawList.length - 1].game_time;
              }
            }
          } else {
            currentApiGameTime = rawList[rawList.length - 1].game_time;
          }
        }
      } else {
        isNoMore.value = true;
      }
    }

    if (queryId === currentQueryId) {
      if (pageNo === 1) {
        sportList.value = accumulatedNewItems;
      } else {
        sportList.value = sportList.value.concat(accumulatedNewItems);
      }
      currentPage.value = pageNo; 
      skeletonLoading.value = false;
      isFetching.value = false;
    }
    
  } catch (error) {
    if (queryId === currentQueryId) {
      isNoMore.value = true;
      skeletonLoading.value = false;
      isFetching.value = false;
    }
  }
}

const onTabChange = (val) => {
  currentState.value = val;
  onQuery(1, currentLimit.value);
};

const dateChange = (e) => {
  currentDate.value = e;
  if (currentState.value !== 1) {
    onQuery(1, currentLimit.value);
  }
};

const goDetail = (item) => {
  router.push({ name: 'SportDetail', query: { data_id: item.data_id } });
};

const getScore = (scoreStr, index) => {
  if (!scoreStr || scoreStr === '-') return '0';
  const parts = scoreStr.split('-');
  return parts[index] || '0';
};

const translateSelection = (valStr) => valStr === undefined || valStr === null ? '' : String(valStr);

// 折叠展开控制
const expandedMatches = ref(new Set());
const expandedMarkets = ref(new Set());

const toggleMatchExpand = (matchId) => {
  const newSet = new Set(expandedMatches.value);
  if (newSet.has(matchId)) newSet.delete(matchId);
  else newSet.add(matchId);
  expandedMatches.value = newSet;
};

const isMatchExpanded = (matchId) => expandedMatches.value.has(matchId);

const toggleMarketExpand = (matchId, marketId) => {
  const key = `${matchId}_${marketId}`;
  const newSet = new Set(expandedMarkets.value);
  if (newSet.has(key)) newSet.delete(key);
  else newSet.add(key);
  expandedMarkets.value = newSet;
};

const isMarketExpanded = (matchId, marketId) => expandedMarkets.value.has(`${matchId}_${marketId}`);

const getExpandedMarketValues = (matchId, market) => {
  if (!market.values) return [];
  if (isMarketExpanded(matchId, market.id)) {
    return market.values;
  }
  return market.values.slice(0, currentCols.value);
};

// 投注选中相关
const getUniqueKey = (matchId, marketId, selection) => {
  if (selection.handicap !== null && selection.handicap !== undefined && selection.handicap !== '') {
    return `${matchId}_${marketId}_${selection.value}_${selection.handicap}`;
  }
  return `${matchId}_${marketId}_${selection.value}`;
};

const syncFromStore = () => {
  const map = {};
  if (orderStore.selectOrders && orderStore.selectOrders.length) {
    orderStore.selectOrders.forEach(order => {
      if (order.uniqueKey) map[order.uniqueKey] = true;
    });
  }
  selectedMap.value = map;
};

watch(() => orderStore.selectOrders, () => {
  syncFromStore();
}, { deep: true, immediate: true });

const isSelected = (matchId, marketId, selection) => {
  const uniqueKey = getUniqueKey(matchId, marketId, selection);
  return !!selectedMap.value[uniqueKey];
};

const handleOddsClick = (item, market, selection) => {
  if (item.state !== 0 && item.state !== 1) return;
  if (item.is_locked || market.is_findLocked) return;
  if (!selection || !selection.odd || selection.odd === '-') return;
  if (selection.suspended) return;

  const uniqueKey = getUniqueKey(item.id, market.id, selection);

  if (selectedMap.value[uniqueKey]) {
    orderStore.removeOrderItemByKey(uniqueKey);
  } else {
    const betItem = {
      matchId: item.id,
      data_id: item.data_id,
      league: item.league,
      league_en: item.league_en,
      homeTeam: item.home_team,
      home_team_en: item.home_team_en,
      guestTeam: item.guest_team,
      guest_team_en: item.guest_team_en,
      betType: market.name,
      betTypeEn: market.name_en,
      betTypeName: selection.value_text,
      betTypeNameEn: selection.value,
      handicap: selection.handicap,
      handicap_text: selection.handicap_text,
      odds: selection.odd,
      score: item.score,
      state: item.state,
      uniqueKey: uniqueKey,
      optionValue: selection.value,
      marketId: market.id,
      selection: {
        ...selection
      }
    };

    orderStore.addOrderItem(betItem);

    if (market.values) {
      market.values.forEach(wsItem => {
        const otherKey = getUniqueKey(item.id, market.id, wsItem);
        if (selectedMap.value[otherKey] && otherKey !== uniqueKey) {
          orderStore.removeOrderItemByKey(otherKey);
        }
      });
    }
  }
};
</script>

<style scoped lang="scss">
$main-color: #f8b932;
$card-bg: var(--el-bg-color-overlay);
$border-color: var(--el-border-color-lighter);
$hover-bg: var(--el-fill-color-light);

/* === 新增：分类与搜索栏样式 === */
.search-segment-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background-color: $card-bg;
  border-radius: 6px;
  padding: 8px 16px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  flex-shrink: 0;
  gap: 16px;

  .el-scrollbar {
    flex: 1;
    min-width: 0;
  }

  .sport-segment-container {
    display: inline-flex;
    gap: 12px;
    align-items: center;
    height: 100%;
  }

  .sport-segment-item {
    display: inline-flex;
    align-items: center;
    padding: 6px 14px;
    border-radius: 20px;
    background-color: var(--el-fill-color-light);
    cursor: pointer;
    transition: all 0.3s;
    white-space: nowrap; /* 核心：防止文字换行 */
    flex-shrink: 0;      /* 核心：防止被压缩 */

    &:hover {
      background-color: var(--el-fill-color);
    }

    &.is-active {
      background-color: $main-color;
      .sport-name, .sport-icon {
        color: #fff;
        font-weight: bold;
      }
    }

    .sport-icon {
      font-size: 16px;
      margin-right: 6px;
    }

    .sport-name {
      font-size: 14px;
      color: var(--el-text-color-regular);
    }
  }

  .search-box {
    width: 260px;
    flex-shrink: 0;
    
    .search-input {
      width: 100%;
    }
  }
}
/* === 新增结束 === */

.odds-btn {
  flex: 1; min-width: 0; background-color: var(--el-fill-color); border: 1px solid var(--el-border-color); border-radius: 4px;
  display: flex; justify-content: space-between; align-items: center; padding: 2px 6px; box-sizing: border-box;
  transition: all 0.2s; cursor: pointer; position: relative; overflow: hidden; flex-direction: column; gap: 6px;

  &.odds-up .odds-val { color: #4caf50 !important; font-weight: bold; }
  &.odds-down .odds-val { color: #f56c6c !important; font-weight: bold; }

  .change-icon {
    margin-left: 4px;
    font-weight: bold;
    font-size: 15px;
    animation: flash 0.8s ease;
    display: flex;align-items: center;
  }

  .change-icon.up { color: #4caf50; }
  .change-icon.down { color: #f56c6c; }
}

@keyframes flash {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.15); }
}

.pc-sports-list-container {
  container-type: inline-size;
  container-name: sports-list;
  height: 100%;
  padding: 6px 8px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  @container sports-list (max-width: 800px) { padding: 4px; }
}

.layout-column {
  display: flex;
  flex-direction: column;
  width: 100%;
  flex: 1;
  min-height: 0;
  gap: 8px;
  background-color: transparent;
  @container sports-list (max-width: 550px) { gap: 6px; }
}

.top-filter-bar {
  background-color: $card-bg;
  border-radius: 6px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px;
  flex-shrink: 0;

  .top-tabs {
    display: flex;
    align-items: center;
    gap: 20px;
  }

  .top-tab-item {
    position: relative;
    padding: 8px 4px;
    cursor: pointer;
    font-size: 14px;
    color: var(--el-text-color-regular);
    transition: all 0.2s;
    font-weight: 500;

    &:hover {
      color: $main-color;
    }
    &.is-active {
      color: $main-color;
      font-weight: bold;
      &::after {
        content: '';
        position: absolute;
        bottom: 0;
        left: 10%;
        height: 3px;
        width: 80%;
        background-color: $main-color;
        border-radius: 2px 2px 0 0;
      }
    }
  }

  .top-actions {
    display: flex;
    align-items: center;
    gap: 12px;

    .el-button {
      display: flex;
      align-items: center;
    }
  }
}

.right-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: calc(100% - 80px);
}

.match-list-body {
  flex: 1; height: 0; display: flex; flex-direction: column;
  @container sports-list (max-width: 550px) { gap: 6px; padding-right: 0; }
  
  :deep(.el-scrollbar) {
    height: 100%;
  }
}

.match-card {
  background-color: $card-bg; border-radius: 6px; border: 1px solid $border-color; box-shadow: 0 1px 3px rgba(0,0,0,0.05); overflow: hidden; flex-shrink: 0; transition: border-color 0.2s; margin-bottom: 8px;
  &:hover { border-color: var(--el-border-color); .team-info { text-decoration: underline; } }

  .match-card-header {
    display: flex; justify-content: space-between; align-items: center;
    padding: 6px 12px;
    background-color: var(--el-fill-color-lighter); border-bottom: 1px solid $border-color;
    @container sports-list (max-width: 550px) { padding: 6px 10px; flex-wrap: wrap; gap: 4px; }
    .league-info {
      display: flex; align-items: center;
      .decorate-bar { width: 3px; height: 12px; background-color: $main-color; border-radius: 2px; margin-right: 6px; }
      .league-name { font-size: 12px; font-weight: bold; color: var(--el-text-color-primary); }
    }
    .match-status-time {
      display: flex; align-items: center;
      .time-text { font-size: 11px; color: var(--el-text-color-regular); font-family: monospace; }
      .timer-text { font-size: 11px; color: var(--el-color-danger); font-weight: bold; display: flex; align-items: center; gap: 4px; .live-dot { width: 4px; height: 4px; background-color: var(--el-color-danger); border-radius: 50%; animation: pulse 1.5s infinite; } }
      .el-tag { height: 18px; line-height: 16px; padding: 0 4px; font-size: 11px; border-radius: 2px; }
    }
  }

  .match-card-main {
    display: flex; align-items: stretch; cursor: pointer; background-color: transparent; transition: background-color 0.2s;
    &:hover { background-color: $hover-bg; }
    @container sports-list (max-width: 550px) { flex-direction: column; }

    .match-teams {
      width: 180px;
      flex-shrink: 0; padding: 8px 12px; border-right: 1px dashed $border-color; display: flex; flex-direction: column; justify-content: center;
      @container sports-list (max-width: 550px) { width: 100%; border-right: none; border-bottom: 1px dashed $border-color; padding: 8px; flex-direction: row; align-items: center; gap: 16px; }
      .team-row {
        display: flex; justify-content: space-between; align-items: center;
        @container sports-list (max-width: 550px) { flex: 1; }
        &.mt-3 { margin-top: 6px !important; @container sports-list (max-width: 550px) { margin-top: 0 !important; } }
        .team-info {
          display: flex; align-items: center; gap: 6px; flex: 1; overflow: hidden;
          .team-logo { background-color: transparent; }
          .team-name { font-size: 12px; font-weight: 500; color: var(--el-text-color-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @container sports-list (max-width: 800px) { font-size: 11px; } }
        }
        .team-score { font-size: 14px; font-weight: 800; color: var(--el-text-color-primary); padding-left: 6px; &.text-live { color: $main-color; } }
      }
    }

    .match-odds-grids {
      flex: 1;
      padding: 8px 12px;
      display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 10px;
      @container sports-list (max-width: 1200px) { gap: 8px; }
      @container sports-list (max-width: 800px) { padding: 6px 8px; gap: 6px; }
      @container sports-list (max-width: 1350px) {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        .market-col:nth-child(n+3) { display: none; }
      }
      @container sports-list (max-width: 992px) {
        grid-template-columns: repeat(1, minmax(0, 1fr));
        .market-col:nth-child(n+2) { display: none; }
      }

      .market-col {
        display: flex; flex-direction: column;
        .market-name { font-size: 11px; color: var(--el-text-color-secondary); margin-bottom: 4px; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
        .market-values { display: flex; gap: 4px; width: 100%; }
      }
      .empty-odds-tip { grid-column: 1 / -1; display: flex; align-items: center; justify-content: flex-start; padding-left: 10px; .empty-text { color: var(--el-text-color-placeholder); font-size: 12px; } }
    }

    .match-more-toggle {
      width: 100px;
      flex-shrink: 0; border-left: 1px dashed $border-color; display: flex; flex-direction: column; align-items: center; justify-content: center; transition: background-color 0.2s;
      @container sports-list (max-width: 800px) { width: 44px; }
      @container sports-list (max-width: 550px) { width: 100%; height: 28px; border-left: none; border-top: 1px dashed $border-color; flex-direction: row; }

      &:hover { background-color: var(--el-border-color-extra-light); }
      .more-content {
        display: flex; flex-direction: column; align-items: center; gap: 2px; color: var(--el-color-primary); font-size: 11px; font-weight: bold;
        @container sports-list (max-width: 550px) { flex-direction: row; }
        .toggle-icon { font-size: 12px; }
      }
      .empty-more { color: var(--el-text-color-placeholder); font-size: 11px;}
    }
  }

  .match-expanded-wrapper {
    background-color: var(--el-fill-color-lighter); border-top: 1px solid $border-color;
  }

  .match-expanded-area {
    padding: 8px 12px 12px 208px;
    display: flex; flex-direction: column; gap: 8px;
    @container sports-list (max-width: 1200px) { padding: 8px 10px 10px 208px; }
    @container sports-list (max-width: 550px) { padding: 6px 8px; gap: 6px; }

    .expanded-market-item {
      background-color: var(--el-bg-color-overlay); border: 1px solid var(--el-border-color-light); border-radius: 4px; padding: 8px; box-shadow: 0 1px 2px rgba(0,0,0,0.02);

      .expanded-market-header {
        display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; padding-bottom: 6px; border-bottom: 1px dashed var(--el-border-color-lighter);
        .header-left { display: flex; align-items: center; .m-decorate { width: 3px; height: 10px; background-color: $main-color; margin-right: 4px; } .m-name { font-size: 12px; font-weight: bold; color: var(--el-text-color-primary); } }
        .el-button { height: 18px; font-size: 11px; padding: 2px; }
      }

      .m-values-grid {
        display: grid; gap: 4px;
      }
    }
  }
}

.odds-btn {
  flex: 1; min-width: 0; background-color: var(--el-fill-color); border: 1px solid var(--el-border-color); border-radius: 4px; display: flex; justify-content: space-between; align-items: center;
  padding: 2px 6px;
  box-sizing: border-box; transition: all 0.2s; cursor: pointer; position: relative; overflow: hidden;
  flex-direction: column;
  gap: 6px;

  .odds-name-wrapper {
    display: flex; align-items: center; min-width: 0; flex: 1; margin-right: 4px; z-index: 1;
    .selection-name {
      @container sports-list (max-width: 4000px) { font-size: 14px;  }
      @container sports-list (max-width: 1200px) { font-size: 13px;  }
      @container sports-list (max-width: 992px) { font-size: 12px;  }
      font-size: 11px; color: var(--el-text-color-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .handicap-val {
      @container sports-list (max-width: 4000px) { font-size: 14px;  }
      @container sports-list (max-width: 1200px) { font-size: 13px;  }
      @container sports-list (max-width: 992px) { font-size: 12px;  }
      flex-shrink: 0; color: var(--el-text-color-primary); margin-left: 2px; font-weight: bold; font-size: 11px;
    }
  }

  .odds-val-wrapper {
    display: flex; align-items: center; flex-shrink: 0; gap: 2px; z-index: 1;
    .odds-val {
      display: flex;
      align-items: center;
      @container sports-list (max-width: 4000px) { font-size: 14px;  }
      @container sports-list (max-width: 1200px) { font-size: 13px;  }
      @container sports-list (max-width: 992px) { font-size: 12px;  }
      font-size: 12px; font-weight: bold; color: $main-color;;
    }
  }

  &:hover { border-color: $main-color; background-color: var(--el-fill-color-light); }
  &.is-active { background-color: $main-color; border-color: $main-color; .odds-name-wrapper .selection-name, .odds-name-wrapper .handicap-val, .odds-val-wrapper .odds-val { color: #fff; font-weight: bold } }
  &.is-locked { cursor: not-allowed; border-color: transparent; .selection-name, .handicap-val, .odds-val { opacity: 0.3; } &::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.4); z-index: 2; } .lock-icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff; font-size: 14px; z-index: 3; } }
}

.market-values {
  container-type: inline-size;
  container-name: odds-wrapper;
}
@container odds-wrapper (max-width: 400px) {
  .odds-btn { flex-direction: column!important; justify-content: center; gap: 1px; }
  .odds-name-wrapper { margin-right: 0!important; }
}

.loading-more {
  text-align: center;
  padding: 15px 0;
  font-size: 13px;
  color: #999;
}

.mr-1 { margin-right: 4px; }
.mr-2 { margin-right: 6px; }
.ml-1 { margin-left: 2px; }

@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }

:deep(.el-avatar) {
  width: 18px !important; height: 18px !important;
  @container sports-list (max-width: 800px) { width: 16px !important; height: 16px !important; }
}
</style>