<template>
  <v-container fluid class="trade-detail-container">
    <v-row align="stretch" no-gutters class="trade-detail-row">
      <!-- 左侧：分时图 + 订单簿（宽度弹性） -->
      <v-col cols="12" class="chart-col">
        <!-- 分时图卡片（Polymarket 样式） -->
        <v-card class="chart-card polymarket-chart" elevation="0" rounded="lg">
          <!-- 顶部：标题、当前概率、Past / 日期 -->
          <div class="chart-header">
            <h1 class="chart-title">
              {{ detailLoading && !eventDetail ? t('common.loading') : marketTitle }}
            </h1>
            <p v-if="detailError" class="chart-error">{{ detailError }}</p>
            <div class="chart-controls-row">
              <v-btn-group
                v-if="isCryptoEvent"
                variant="outlined"
                density="compact"
                divided
                class="chart-mode-toggle"
              >
                <v-btn
                  :class="{ active: chartMode === 'yesno' }"
                  size="small"
                  icon
                  :aria-label="t('chart.yesnoTimeSeries')"
                  @click="setChartMode('yesno')"
                >
                  <v-icon size="20">mdi-chart-timeline-variant</v-icon>
                </v-btn>
                <v-btn
                  :class="{ active: chartMode === 'crypto' }"
                  size="small"
                  icon
                  :aria-label="t('chart.cryptoPrice')"
                  @click="setChartMode('crypto')"
                >
                  <v-icon size="20">mdi-currency-btc</v-icon>
                </v-btn>
              </v-btn-group>
              <v-btn variant="text" size="small" class="past-btn">Past ▾</v-btn>
              <v-btn class="date-pill" size="small" rounded="pill">{{ resolutionDate }}</v-btn>
            </div>
            <div class="chart-chance">
              <template v-if="chartMode === 'crypto' && cryptoSymbol">
                ${{ currentCryptoPrice }} {{ cryptoSymbol.toUpperCase() }}
              </template>
              <template v-else> {{ currentChance }}% {{ t('common.chance') }} </template>
            </div>
          </div>

          <!-- 图表区域 -->
          <div class="chart-wrapper">
            <div v-if="cryptoChartLoading || chartYesNoLoading" class="chart-loading-overlay">
              <v-progress-circular indeterminate color="primary" size="32" />
            </div>
            <div ref="chartContainerRef" class="chart-container"></div>
          </div>

          <!-- 底部：成交量/到期日 | 时间粒度 -->
          <div class="chart-footer">
            <div class="chart-footer-left">
              <span class="chart-volume">{{ marketVolume }} Vol.</span>
              <span v-if="marketExpiresAt" class="chart-expires">| {{ marketExpiresAt }}</span>
            </div>
            <div class="chart-time-ranges">
              <v-btn
                v-for="r in timeRanges"
                :key="r.value"
                :class="['time-range-btn', { active: selectedTimeRange === r.value }]"
                variant="text"
                size="small"
                @click="selectTimeRange(r.value)"
              >
                {{ r.label }}
              </v-btn>
            </div>
          </div>
        </v-card>

        <!-- 持仓 / 限价（订单簿上方） -->
        <v-card class="positions-orders-card" elevation="0" rounded="lg">
          <v-tabs v-model="positionsOrdersTab" class="positions-orders-tabs" density="comfortable">
            <v-tab value="positions">{{ t('activity.myPositions') }}</v-tab>
            <v-tab value="orders">{{ t('activity.openOrders') }}</v-tab>
          </v-tabs>
          <v-window v-model="positionsOrdersTab" class="positions-orders-window">
            <v-window-item value="positions" class="detail-pane">
              <div v-if="positionLoading" class="placeholder-pane">{{ t('common.loading') }}</div>
              <div v-else-if="marketPositionsFiltered.length === 0" class="placeholder-pane">
                {{ t('activity.noPositionsInMarket') }}
              </div>
              <div v-else class="positions-list">
                <div v-for="pos in marketPositionsFiltered" :key="pos.id" class="position-row-item">
                  <div class="position-row-header">
                    <div class="position-row-icon" :class="pos.iconClass">
                      <img
                        v-if="pos.imageUrl"
                        :src="pos.imageUrl"
                        alt=""
                        class="position-row-icon-img"
                      />
                      <span v-else class="position-row-icon-char">{{ pos.iconChar || '•' }}</span>
                    </div>
                    <span class="position-row-title">{{ pos.market }}</span>
                  </div>
                  <div class="position-row-main">
                    <span :class="['position-outcome-pill', pos.outcomePillClass]">{{
                      pos.outcomeTag
                    }}</span>
                    <span v-if="pos.locked" class="position-lock-badge">
                      {{
                        pos.lockedSharesNum != null && Number.isFinite(pos.lockedSharesNum)
                          ? t('activity.positionLockedWithAmount', { n: pos.lockedSharesNum })
                          : t('activity.positionLocked')
                      }}
                    </span>
                    <span class="position-shares">{{ pos.shares }}</span>
                    <span class="position-value">{{ pos.value }}</span>
                    <v-btn
                      variant="outlined"
                      size="small"
                      color="primary"
                      class="position-sell-btn"
                      :disabled="!(pos.availableSharesNum != null && pos.availableSharesNum > 0)"
                      @click="openSellFromPosition(pos)"
                    >
                      {{ t('trade.sell') }}
                    </v-btn>
                  </div>
                  <div class="position-row-meta">{{ pos.bet }} → {{ pos.toWin }}</div>
                </div>
              </div>
            </v-window-item>
            <v-window-item value="orders" class="detail-pane">
              <div v-if="openOrderLoading" class="placeholder-pane">{{ t('common.loading') }}</div>
              <div v-else-if="marketOpenOrders.length === 0" class="placeholder-pane">
                {{ t('activity.noOpenOrdersInMarket') }}
              </div>
              <div v-else class="orders-list">
                <div v-for="ord in marketOpenOrders" :key="ord.id" class="order-row-item">
                  <div class="order-row-main">
                    <span :class="['order-side-pill', ord.side === 'Yes' ? 'side-yes' : 'side-no']">
                      {{ ord.actionLabel || `Buy ${ord.outcome}` }}
                    </span>
                    <span class="order-price">{{ ord.price }}</span>
                    <span class="order-filled">{{ ord.filled }}</span>
                    <span class="order-total">{{ ord.total }}</span>
                  </div>
                  <div class="order-row-actions">
                    <v-btn
                      variant="text"
                      size="small"
                      color="error"
                      :disabled="cancelOrderLoading || ord.fullyFilled"
                      @click="cancelMarketOrder(ord)"
                    >
                      {{ t('activity.cancelOrder') }}
                    </v-btn>
                  </div>
                </div>
              </div>
            </v-window-item>
          </v-window>
        </v-card>

        <!-- Order Book Section -->
        <v-card class="order-book-card" elevation="0" rounded="lg">
          <OrderBook
            :asks-yes="orderBookAsksYes"
            :bids-yes="orderBookBidsYes"
            :asks-no="orderBookAsksNo"
            :bids-no="orderBookBidsNo"
            :last-price-yes="clobLastPriceYes"
            :last-price-no="clobLastPriceNo"
            :spread-yes="clobSpreadYes"
            :spread-no="clobSpreadNo"
            :loading="clobLoading"
            :connected="clobConnected"
            :yes-label="yesLabel"
            :no-label="noLabel"
          />
        </v-card>

        <!-- Comments / Top Holders / Activity（与左侧图表、订单簿同宽） -->
        <v-card class="activity-card" elevation="0" rounded="lg">
          <v-tabs v-model="detailTab" class="detail-tabs" density="comfortable">
            <v-tab value="rules">{{ t('activity.rules') }}</v-tab>
            <v-tab value="holders">{{ t('activity.topHolders') }}</v-tab>
            <v-tab value="activity">{{ t('activity.activity') }}</v-tab>
          </v-tabs>
          <v-window v-model="detailTab" class="detail-window">
            <v-window-item value="rules" class="detail-pane">
              <div class="rules-pane">
                <div
                  v-if="!eventDetail?.description && !eventDetail?.resolutionSource"
                  class="placeholder-pane"
                >
                  {{ t('activity.rulesEmpty') }}
                </div>
                <template v-else>
                  <div v-if="eventDetail?.description" class="rules-section">
                    <h3 class="rules-title">{{ t('activity.rulesDescription') }}</h3>
                    <div class="rules-text">{{ eventDetail.description }}</div>
                  </div>
                  <div v-if="eventDetail?.resolutionSource" class="rules-section">
                    <h3 class="rules-title">{{ t('activity.rulesSource') }}</h3>
                    <a
                      v-if="isResolutionSourceUrl"
                      :href="eventDetail.resolutionSource"
                      target="_blank"
                      rel="noopener noreferrer"
                      class="rules-link"
                    >
                      {{ eventDetail.resolutionSource }}
                      <v-icon size="14">mdi-open-in-new</v-icon>
                    </a>
                    <div v-else class="rules-text">{{ eventDetail.resolutionSource }}</div>
                  </div>
                </template>
              </div>
            </v-window-item>
            <v-window-item value="holders" class="detail-pane">
              <div class="placeholder-pane">{{ t('activity.topHoldersPlaceholder') }}</div>
            </v-window-item>
            <v-window-item value="activity" class="detail-pane">
              <div class="activity-toolbar">
                <v-select
                  v-model="activityMinAmount"
                  :items="minAmountOptions"
                  density="compact"
                  hide-details
                  variant="outlined"
                  :label="t('activity.minAmount')"
                  class="min-amount-select"
                />
                <span class="live-badge">
                  <span class="live-dot"></span>
                  {{ t('activity.live') }}
                </span>
              </div>
              <div class="activity-list">
                <div v-for="item in filteredActivity" :key="item.id" class="activity-item">
                  <div class="activity-avatar" :class="item.avatarClass">
                    <img
                      v-if="item.avatarUrl"
                      :src="item.avatarUrl"
                      :alt="item.user"
                      class="avatar-img"
                    />
                  </div>
                  <div class="activity-body">
                    <span class="activity-user">{{ item.user }}</span>
                    <span class="activity-action">{{
                      t(item.action === 'bought' ? 'activity.bought' : 'activity.sold')
                    }}</span>
                    <span
                      :class="['activity-amount', item.side === 'Yes' ? 'amount-yes' : 'amount-no']"
                    >
                      {{ item.amount }} {{ item.side }}
                    </span>
                    <span class="activity-price">{{ t('activity.at') }} {{ item.price }}</span>
                    <span class="activity-total">({{ item.total }})</span>
                  </div>
                  <div class="activity-meta">
                    <span class="activity-time">{{ formatTimeAgo(item.time) }}</span>
                    <a
                      href="#"
                      class="activity-link"
                      :aria-label="t('activity.viewTransaction')"
                      @click.prevent
                    >
                      <v-icon size="16">mdi-open-in-new</v-icon>
                    </a>
                  </div>
                </div>
              </div>
            </v-window-item>
          </v-window>
        </v-card>
      </v-col>

      <!-- 右侧：交易组件（固定宽度），传入当前市场以便 Split 调用拆单接口；移动端隐藏，改用底部栏+弹窗 -->
      <v-col v-if="!isMobile" cols="12" class="trade-col">
        <div class="trade-sidebar">
          <TradeComponent
            ref="tradeComponentRef"
            :market="tradeMarketPayload"
            :initial-option="tradeInitialOption"
            :positions="tradePositionsForComponent"
            @merge-success="onMergeSuccess"
            @split-success="onSplitSuccess"
          />
        </div>
      </v-col>

      <!-- 移动端：固定底部 Yes/No 栏 + 三点菜单（Merge/Split） -->
      <template v-if="isMobile && tradeMarketPayload">
        <div class="mobile-trade-bar-spacer" aria-hidden="true"></div>
        <div class="mobile-trade-bar">
          <v-btn
            class="mobile-bar-btn mobile-bar-yes"
            variant="flat"
            rounded="sm"
            @click="openSheetWithOption('yes')"
          >
            {{ yesLabel }} {{ yesPriceCents }}¢
          </v-btn>
          <v-btn
            class="mobile-bar-btn mobile-bar-no"
            variant="flat"
            rounded="sm"
            @click="openSheetWithOption('no')"
          >
            {{ noLabel }} {{ noPriceCents }}¢
          </v-btn>
          <v-menu
            v-model="mobileMenuOpen"
            :close-on-content-click="true"
            location="top"
            transition="scale-transition"
          >
            <template #activator="{ props: menuProps }">
              <v-btn
                v-bind="menuProps"
                class="mobile-bar-more-btn"
                variant="flat"
                icon
                rounded="pill"
                aria-label="更多操作"
              >
                <v-icon size="20">mdi-dots-horizontal</v-icon>
              </v-btn>
            </template>
            <v-list density="compact">
              <v-list-item @click="openMergeFromBar">
                <v-list-item-title>{{ t('trade.merge') }}</v-list-item-title>
              </v-list-item>
              <v-list-item @click="openSplitFromBar">
                <v-list-item-title>{{ t('trade.split') }}</v-list-item-title>
              </v-list-item>
            </v-list>
          </v-menu>
        </div>
        <v-bottom-sheet v-model="tradeSheetOpen" content-class="trade-detail-trade-sheet">
          <TradeComponent
            v-if="tradeSheetRenderContent"
            ref="mobileTradeComponentRef"
            :market="tradeMarketPayload"
            :initial-option="tradeInitialOptionFromBar"
            :initial-tab="tradeInitialTabFromBar"
            :positions="tradePositionsForComponent"
            embedded-in-sheet
            @order-success="onOrderSuccess"
            @merge-success="onMergeSuccess"
            @split-success="onSplitSuccess"
          />
        </v-bottom-sheet>
      </template>

      <!-- 从持仓点击 Sell 弹出的交易组件（桌面/移动端通用） -->
      <v-dialog
        v-model="sellDialogOpen"
        max-width="420"
        content-class="trade-detail-sell-dialog"
        transition="dialog-transition"
      >
        <TradeComponent
          v-if="sellDialogRenderContent"
          :market="tradeMarketPayload"
          :initial-option="sellInitialOption"
          :initial-tab="'sell'"
          :positions="tradePositionsForComponent"
          @order-success="onSellOrderSuccess"
          @merge-success="onMergeSuccess"
          @split-success="onSplitSuccess"
        />
      </v-dialog>
    </v-row>
  </v-container>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify'
import {
  createChart,
  LineSeries,
  LineType,
  LastPriceAnimationMode,
  type IChartApi,
  type ISeriesApi,
} from 'lightweight-charts'
import { toLwcData, toLwcPoint } from '../composables/useLightweightChart'
import OrderBook from '../components/OrderBook.vue'
import TradeComponent, { type TradePositionItem } from '../components/TradeComponent.vue'
import {
  findPmEvent,
  getMarketId,
  getClobTokenId,
  type FindPmEventParams,
  type PmEventListItem,
} from '../api/event'
import { getClobWsUrl } from '../api/request'
import { useUserStore } from '../stores/user'
import { useToastStore } from '../stores/toast'
import { useLocaleStore } from '../stores/locale'
import { useAuthError } from '../composables/useAuthError'
import {
  getPositionList,
  mapPositionToDisplayItem,
  type PositionDisplayItem,
} from '../api/position'
import {
  getOrderList,
  mapOrderToOpenOrderItem,
  OrderStatus,
  type OpenOrderDisplayItem,
} from '../api/order'
import { cancelOrder as apiCancelOrder } from '../api/order'
import type { ChartDataPoint, ChartTimeRange } from '../api/chart'
import {
  getPmPriceHistoryPublic,
  getTimeRangeSeconds,
  priceHistoryToChartData,
} from '../api/priceHistory'
import {
  isCryptoEvent as checkIsCryptoEvent,
  inferCryptoSymbol,
  fetchCryptoChart,
  subscribeCryptoRealtime,
} from '../api/cryptoChart'

const { t } = useI18n()
import { ClobSdk, type PriceSizePolyMsg, type TradePolyMsg } from '../../sdk/clobSocket'

/**
 * 分时图服务端推送数据格式约定
 *
 * 图表所需单点格式（与 ECharts 时间轴一致）：
 *   [timestamp, value]
 *   - timestamp: number，Unix 毫秒时间戳
 *   - value: number，当前概率/价格（如 0–100 的百分比）
 *
 * 服务端可选用两种推送方式：
 *
 * 1) 全量快照（切换时间粒度或首次加载时）：
 *    { range?: string, data: [number, number][] }
 *    - range: 可选，'1H' | '6H' | '1D' | '1W' | '1M' | 'ALL'
 *    - data: 按时间升序的 [timestamp, value] 数组
 *
 * 2) 增量点（实时推送最新点）：
 *    { point: [number, number] }
 *    - point: 单点 [timestamp, value]，前端会追加并按当前 range 保留最近 N 个点
 *
 * 示例（WebSocket 消息体）：
 *   全量: { "data": [[1707206400000, 25.5], [1707210000000, 26.1], ...] }
 *   增量: { "point": [1707213600000, 27.2] }
 */
export type ChartPoint = [number, number]
export type ChartSnapshot = { range?: string; data: ChartPoint[] }
export type ChartIncrement = { point: ChartPoint }

const route = useRoute()
const userStore = useUserStore()
const { formatAuthError } = useAuthError()
const localeStore = useLocaleStore()
const { mobile } = useDisplay()
const isMobile = computed(() => mobile.value)

// 详情接口 GET /PmEvent/findPmEvent 返回的数据
const eventDetail = ref<PmEventListItem | null>(null)
const detailLoading = ref(false)
const detailError = ref<string | null>(null)

function formatVolume(volume: number | undefined): string {
  if (volume == null || !Number.isFinite(volume)) return '$0 Vol.'
  if (volume >= 1000) return `$${(volume / 1000).toFixed(1)}k Vol.`
  return `$${Math.round(volume)} Vol.`
}

function formatExpiresAt(endDate: string | undefined): string {
  if (!endDate) return ''
  try {
    const d = new Date(endDate)
    if (Number.isNaN(d.getTime())) return endDate
    return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
  } catch {
    return endDate
  }
}

async function loadEventDetail() {
  const idRaw = route.params.id
  const idStr = String(idRaw ?? '').trim()
  if (!idStr) {
    detailError.value = t('error.invalidId')
    eventDetail.value = null
    return
  }
  const numId = parseInt(idStr, 10)
  const isNumericId = Number.isFinite(numId) && String(numId) === idStr && numId >= 1
  const slugFromQuery = (route.query.slug as string)?.trim()
  const params: FindPmEventParams = {
    id: isNumericId ? numId : undefined,
    slug: isNumericId ? slugFromQuery || undefined : idStr,
  }

  detailError.value = null
  detailLoading.value = true
  try {
    const res = await findPmEvent(params, {
      headers: userStore.getAuthHeaders(),
    })
    if (res.code === 0 || res.code === 200) {
      eventDetail.value = res.data ?? null
      // 单个市场详情数据打印，便于分析
      const ev = eventDetail.value
      console.log('[TradeDetail] 单个市场详情:', {
        id: ev?.ID ?? ev?.id,
        title: ev?.title,
        slug: ev?.slug,
        volume: ev?.volume,
        endDate: ev?.endDate,
        marketsCount: ev?.markets?.length ?? 0,
        firstMarket: ev?.markets?.[0]
          ? {
              question: ev.markets[0].question,
              outcomePrices: ev.markets[0].outcomePrices,
              marketId: ev.markets[0].marketId,
            }
          : null,
      })
    } else {
      detailError.value = res.msg || t('error.loadFailed')
      eventDetail.value = null
    }
  } catch (e) {
    detailError.value = formatAuthError(e, t('error.loadFailed'))
    eventDetail.value = null
  } finally {
    detailLoading.value = false
  }
}

// 标题、成交量、到期日：优先接口详情，其次卡片 query，最后占位
const marketTitle = computed(() => {
  if (eventDetail.value?.title) return eventDetail.value.title
  return (route.query.title as string) || 'U.S. anti-cartel ground operation in Mexico by March 31?'
})
const marketVolume = computed(() => {
  if (eventDetail.value?.volume != null) return formatVolume(eventDetail.value.volume)
  return (route.query.marketInfo as string) || '$398,719'
})
const marketExpiresAt = computed(() => {
  if (eventDetail.value?.endDate) return formatExpiresAt(eventDetail.value.endDate)
  return (route.query.expiresAt as string) || 'Mar 31, 2026'
})
const resolutionDate = computed(() => {
  const s = marketExpiresAt.value
  return s ? s.replace(/,?\s*\d{4}$/, '').trim() || 'Mar 31' : 'Mar 31'
})

const isResolutionSourceUrl = computed(() => {
  const src = eventDetail.value?.resolutionSource
  return typeof src === 'string' && (src.startsWith('http://') || src.startsWith('https://'))
})

/** 是否为加密货币类型事件（可切换 YES/NO 分时 vs 加密货币价格图） */
const isCryptoEvent = computed(() => checkIsCryptoEvent(eventDetail.value))

/** 从事件推断的加密货币符号，如 btc、eth */
const cryptoSymbol = computed(() => inferCryptoSymbol(eventDetail.value ?? {}) ?? null)

/** 图表模式：yesno=YES/NO 分时，crypto=加密货币价格 */
const chartMode = ref<'yesno' | 'crypto'>('yesno')

/** 加密货币模式下当前价格（最新数据点） */
const currentCryptoPrice = computed(() => {
  const d = data.value
  const last = d.length > 0 ? d[d.length - 1] : undefined
  if (last != null && chartMode.value === 'crypto') {
    const p = last[1]
    return Number.isFinite(p)
      ? p.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
      : '—'
  }
  return '—'
})

function setChartMode(mode: 'yesno' | 'crypto') {
  if (mode === 'yesno' && selectedTimeRange.value === '30S') {
    selectedTimeRange.value = '1D'
  } else if (mode === 'crypto') {
    selectedTimeRange.value = '30S'
  }
  chartMode.value = mode
  updateChartData()
}

/** 当前市场（用于交易组件与 Split 拆单）：query.marketId 匹配或取第一个 */
const currentMarket = computed(() => {
  const list = eventDetail.value?.markets ?? []
  if (list.length === 0) return null
  const qId = route.query.marketId
  if (qId != null && String(qId).trim() !== '') {
    const qStr = String(qId).trim()
    const found = list.find((m) => getMarketId(m) === qStr)
    if (found) return found
  }
  return list[0]
})

// --- CLOB WebSocket 订单簿与成交 ---
// 按 token 索引区分：0 = Yes，1 = No
type OrderBookRows = { price: number; shares: number; priceRaw?: number }[]
const clobSdkRef = ref<ClobSdk | null>(null)
const orderBookByToken = ref<Record<number, { asks: OrderBookRows; bids: OrderBookRows }>>({
  0: { asks: [], bids: [] },
  1: { asks: [], bids: [] },
})
const clobLastPriceByToken = ref<Record<number, number | undefined>>({ 0: undefined, 1: undefined })
const clobSpreadByToken = ref<Record<number, number | undefined>>({ 0: undefined, 1: undefined })
const clobConnected = ref(false)
const clobLoading = ref(false)
/** 当前订阅的 tokenIds，用于根据 msg.m 匹配 Yes(0)/No(1) */
const clobTokenIdsRef = ref<string[]>([])

const orderBookAsksYes = computed(() => orderBookByToken.value[0]?.asks ?? [])
const orderBookBidsYes = computed(() => orderBookByToken.value[0]?.bids ?? [])
const orderBookAsksNo = computed(() => orderBookByToken.value[1]?.asks ?? [])
const orderBookBidsNo = computed(() => orderBookByToken.value[1]?.bids ?? [])

/** 订单簿 Yes 卖单最低价（分），无数据时为 0 */
const orderBookLowestAskYesCents = computed(() => {
  const asks = orderBookAsksYes.value
  if (!asks.length) return 0
  return Math.min(...asks.map((a) => a.price))
})
/** 订单簿 No 卖单最低价（分），无数据时为 0 */
const orderBookLowestAskNoCents = computed(() => {
  const asks = orderBookAsksNo.value
  if (!asks.length) return 0
  return Math.min(...asks.map((a) => a.price))
})
/** 订单簿 Yes 买单最高价（分），无数据时为 0；市价卖出时用于计算将收到金额 */
const orderBookBestBidYesCents = computed(() => {
  const bids = orderBookBidsYes.value
  if (!bids.length) return 0
  return Math.max(...bids.map((b) => b.price))
})
/** 订单簿 No 买单最高价（分），无数据时为 0；市价卖出时用于计算将收到金额 */
const orderBookBestBidNoCents = computed(() => {
  const bids = orderBookBidsNo.value
  if (!bids.length) return 0
  return Math.max(...bids.map((b) => b.price))
})
const clobLastPriceYes = computed(() => clobLastPriceByToken.value[0])
const clobLastPriceNo = computed(() => clobLastPriceByToken.value[1])
const clobSpreadYes = computed(() => clobSpreadByToken.value[0])
const clobSpreadNo = computed(() => clobSpreadByToken.value[1])

/** 订单簿份额接口按 6 位小数传（1_000_000 = 1 share），需除以该系数转为展示值 */
const ORDER_BOOK_SIZE_SCALE = 1_000_000

/** 接口 key p 为价格（与 price 美分同源，除以 100 为美分）；priceRaw 为美元单价保留原始精度供订单簿算总价 */
function priceSizeToRows(record: Record<string, number> | undefined): OrderBookRows {
  if (!record) return []
  return Object.entries(record)
    .filter(([, rawShares]) => rawShares > 0)
    .map(([p, rawShares]) => {
      const numP = parseFloat(p)
      const priceCents = Math.round(numP / 100)
      const priceRawDollars = Number.isFinite(numP) ? numP / 10000 : priceCents / 100
      return {
        price: priceCents,
        shares: rawShares / ORDER_BOOK_SIZE_SCALE,
        priceRaw: priceRawDollars,
      }
    })
}

function getTokenIndex(msg: PriceSizePolyMsg): number {
  const ids = clobTokenIdsRef.value
  const m = msg.m
  if (m != null && ids.length >= 2) {
    const idx = ids.findIndex((id) => String(id) === String(m))
    if (idx === 0 || idx === 1) return idx
  }
  if (typeof msg.i === 'number' && (msg.i === 0 || msg.i === 1)) return msg.i
  return 0
}

function applyPriceSizeAll(msg: PriceSizePolyMsg) {
  const idx = getTokenIndex(msg)
  const asks = priceSizeToRows(msg.s).sort((a, b) => a.price - b.price)
  const bids = priceSizeToRows(msg.b).sort((a, b) => b.price - a.price)
  orderBookByToken.value = {
    ...orderBookByToken.value,
    [idx]: { asks, bids },
  }
  updateSpreadForToken(idx)
}

function applyPriceSizeDelta(msg: PriceSizePolyMsg) {
  const idx = getTokenIndex(msg)
  const mergeDelta = (
    current: OrderBookRows,
    delta: Record<string, number> | undefined,
    asc: boolean,
  ) => {
    const map = new Map<string, { price: number; shares: number; priceRaw: number }>()
    const keyOf = (price: number, priceRaw: number) => priceRaw.toFixed(8)
    current.forEach((r) => {
      const pr = r.priceRaw ?? r.price / 100
      map.set(keyOf(r.price, pr), { price: r.price, shares: r.shares, priceRaw: pr })
    })
    if (delta) {
      Object.entries(delta).forEach(([p, rawShares]) => {
        const numP = parseFloat(p)
        const price = Math.round(numP / 100)
        const priceRaw = Number.isFinite(numP) ? numP / 10000 : price / 100
        const shares = rawShares / ORDER_BOOK_SIZE_SCALE
        const key = keyOf(price, priceRaw)
        if (shares <= 0) map.delete(key)
        else map.set(key, { price, shares, priceRaw })
      })
    }
    return Array.from(map.values()).sort((a, b) => (asc ? a.price - b.price : b.price - a.price))
  }
  const prev = orderBookByToken.value[idx] ?? { asks: [], bids: [] }
  const asks = mergeDelta(prev.asks, msg.s, true)
  const bids = mergeDelta(prev.bids, msg.b, false)
  orderBookByToken.value = {
    ...orderBookByToken.value,
    [idx]: { asks, bids },
  }
  updateSpreadForToken(idx)
}

function updateSpreadForToken(idx: number) {
  const book = orderBookByToken.value[idx]
  if (!book) return
  const bestAsk = book.asks[0]?.price
  const bestBid = book.bids[0]?.price
  if (bestAsk != null && bestBid != null) {
    clobSpreadByToken.value = { ...clobSpreadByToken.value, [idx]: bestAsk - bestBid }
  }
}

function connectClob(tokenIds: string[]) {
  clobSdkRef.value?.disconnect()
  clobSdkRef.value = null
  clobLoading.value = true
  clobConnected.value = false
  clobTokenIdsRef.value = tokenIds
  orderBookByToken.value = { 0: { asks: [], bids: [] }, 1: { asks: [], bids: [] } }
  clobLastPriceByToken.value = { 0: undefined, 1: undefined }
  clobSpreadByToken.value = { 0: undefined, 1: undefined }

  const options = {
    url: getClobWsUrl(),
    autoReconnect: true,
    reconnectInterval: 2000,
  }
  const sdk = new ClobSdk(tokenIds, options)

  sdk.onConnect(() => {
    clobConnected.value = true
    clobLoading.value = false
  })
  sdk.onDisconnect(() => {
    clobConnected.value = false
  })
  sdk.onError(() => {
    clobLoading.value = false
  })
  sdk.onPriceSizeAll(applyPriceSizeAll)
  sdk.onPriceSizeDelta(applyPriceSizeDelta)
  sdk.onTrade((msg: TradePolyMsg) => {
    const priceNum = parseFloat(msg.p)
    if (Number.isFinite(priceNum)) {
      const priceCents = Math.round(priceNum * 100)
      const side = msg.side?.toLowerCase()
      const tokenIdx = side === 'buy' ? 0 : 1
      clobLastPriceByToken.value = { ...clobLastPriceByToken.value, [tokenIdx]: priceCents }
    }
    // 追加到 Activity 列表
    const side = msg.side?.toLowerCase() === 'buy' ? 'Yes' : 'No'
    const action = side === 'Yes' ? 'bought' : 'sold'
    activityList.value.unshift({
      id: `clob-${Date.now()}-${Math.random().toString(36).slice(2)}`,
      user: '0x...',
      avatarClass: 'avatar-gradient-1',
      action: action as 'bought' | 'sold',
      side: side as 'Yes' | 'No',
      amount: Math.round(parseFloat(msg.s) || 0),
      price: `${Math.round((priceNum || 0) * 100)}¢`,
      total: `$${((parseFloat(msg.s) || 0) * (priceNum || 0)).toFixed(2)}`,
      time: Date.now(),
    })
  })

  clobSdkRef.value = sdk
  sdk.connect()
}

function disconnectClob() {
  clobSdkRef.value?.disconnect()
  clobSdkRef.value = null
  clobConnected.value = false
  clobLoading.value = false
}

/** 传给 TradeComponent 的 market，供 Split 调用 /PmMarket/split；yesPrice/noPrice 取订单簿卖单最低价，无数据时为 0 */
const tradeMarketPayload = computed(() => {
  const m = currentMarket.value
  const yesPrice = orderBookLowestAskYesCents.value / 100
  const noPrice = orderBookLowestAskNoCents.value / 100
  const bestBidYesCents = orderBookBestBidYesCents.value
  const bestBidNoCents = orderBookBestBidNoCents.value
  if (m) {
    return {
      marketId: getMarketId(m),
      yesPrice,
      noPrice,
      title: m.question,
      clobTokenIds: m.clobTokenIds,
      outcomes: m.outcomes,
      bestBidYesCents,
      bestBidNoCents,
    }
  }
  const qId = route.query.marketId
  if (qId != null && String(qId).trim() !== '') {
    return {
      marketId: String(qId).trim(),
      yesPrice,
      noPrice,
      title: (route.query.title as string) || undefined,
      bestBidYesCents,
      bestBidNoCents,
    }
  }
  return undefined
})

const tradeInitialOption = computed(() => {
  const side = route.query.side
  if (side === 'yes' || side === 'no') return side
  return undefined
})

/** 移动端底部栏点击 Yes/No 时传给弹窗内 TradeComponent 的初始选项 */
const tradeInitialOptionFromBar = ref<'yes' | 'no' | undefined>(undefined)
/** 移动端弹窗初始 Tab：从持仓 Sell 打开时为 'sell'，从底部栏 Yes/No 打开时为 undefined（默认 Buy） */
const tradeInitialTabFromBar = ref<'buy' | 'sell' | undefined>(undefined)
/** 移动端交易弹窗开关 */
const tradeSheetOpen = ref(false)
/** 控制底部栏内 TradeComponent 的渲染，延迟挂载以避免 slot 竞态警告 */
const tradeSheetRenderContent = ref(false)
/** 从三点菜单点击 Merge/Split 时待打开的弹窗，等 TradeComponent 挂载后执行 */
const pendingMergeSplitDialog = ref<'merge' | 'split' | null>(null)
/** 从持仓 Sell 打开的弹窗 */
const sellDialogOpen = ref(false)
/** 控制 Sell 弹窗内 TradeComponent 的渲染，延迟卸载以避免 emitsOptions 竞态 */
const sellDialogRenderContent = ref(false)
/** 从持仓 Sell 时预选的 Yes/No */
const sellInitialOption = ref<'yes' | 'no'>('yes')
/** 移动端三点菜单开关 */
const mobileMenuOpen = ref(false)
/** 桌面端 TradeComponent 引用（Merge/Split） */
const tradeComponentRef = ref<InstanceType<typeof TradeComponent> | null>(null)
/** 移动端弹窗内 TradeComponent 引用 */
const mobileTradeComponentRef = ref<InstanceType<typeof TradeComponent> | null>(null)

const yesPriceCents = computed(() =>
  tradeMarketPayload.value ? Math.round(tradeMarketPayload.value.yesPrice * 100) : 0,
)
const noPriceCents = computed(() =>
  tradeMarketPayload.value ? Math.round(tradeMarketPayload.value.noPrice * 100) : 0,
)
const yesLabel = computed(() => currentMarket.value?.outcomes?.[0] ?? 'Yes')
const noLabel = computed(() => currentMarket.value?.outcomes?.[1] ?? 'No')

function openSheetWithOption(side: 'yes' | 'no') {
  tradeInitialOptionFromBar.value = side
  tradeInitialTabFromBar.value = undefined
  tradeSheetOpen.value = true
}

function openMergeFromBar() {
  mobileMenuOpen.value = false
  pendingMergeSplitDialog.value = 'merge'
  tradeSheetOpen.value = true
}

function openSplitFromBar() {
  mobileMenuOpen.value = false
  pendingMergeSplitDialog.value = 'split'
  tradeSheetOpen.value = true
}

const toastStore = useToastStore()
function onOrderSuccess() {
  tradeSheetOpen.value = false
  toastStore.show(t('toast.orderSuccess'))
  loadMarketPositions()
  loadMarketOpenOrders()
}

/** 合并成功后刷新持仓（根据 tokenId 更新本地持仓数据） */
function onMergeSuccess() {
  toastStore.show(t('toast.mergeSuccess'))
  loadMarketPositions()
}

/** 拆分成功后刷新持仓 */
function onSplitSuccess() {
  loadMarketPositions()
}

/** 从持仓项点击 Sell：弹出交易组件并切到 Sell、对应 Yes/No。持仓类型用 outcome 字段与 noLabel 匹配。 */
function openSellFromPosition(pos: PositionDisplayItem) {
  if (pos.availableSharesNum == null || pos.availableSharesNum <= 0) {
    toastStore.show(t('activity.noAvailableSharesToSell'), 'warning')
    return
  }
  const option = pos.outcome === noLabel.value ? 'no' : 'yes'
  if (isMobile.value) {
    tradeInitialOptionFromBar.value = option
    tradeInitialTabFromBar.value = 'sell'
    tradeSheetOpen.value = true
  } else {
    sellInitialOption.value = option
    sellDialogOpen.value = true
  }
}

function onSellOrderSuccess() {
  sellDialogOpen.value = false
  onOrderSuccess()
}

// 延迟卸载 Sell 弹窗内容，等 dialog transition 完成，避免 emitsOptions 竞态
let sellDialogUnmountTimer: ReturnType<typeof setTimeout> | undefined
watch(
  sellDialogOpen,
  (open) => {
    if (open) {
      if (sellDialogUnmountTimer) {
        clearTimeout(sellDialogUnmountTimer)
        sellDialogUnmountTimer = undefined
      }
      sellDialogRenderContent.value = true
    } else {
      sellDialogUnmountTimer = setTimeout(() => {
        sellDialogRenderContent.value = false
        sellDialogUnmountTimer = undefined
      }, 350)
    }
  },
  { immediate: true },
)
onUnmounted(() => {
  if (sellDialogUnmountTimer) clearTimeout(sellDialogUnmountTimer)
  if (tradeSheetUnmountTimer) clearTimeout(tradeSheetUnmountTimer)
  if (tradeSheetMountTimer) clearTimeout(tradeSheetMountTimer)
})

// 底部栏 TradeComponent 延迟挂载/卸载，避免 transition 期间 slot 竞态
let tradeSheetUnmountTimer: ReturnType<typeof setTimeout> | undefined
let tradeSheetMountTimer: ReturnType<typeof setTimeout> | undefined
watch(
  tradeSheetOpen,
  (open) => {
    if (open) {
      if (tradeSheetUnmountTimer) {
        clearTimeout(tradeSheetUnmountTimer)
        tradeSheetUnmountTimer = undefined
      }
      if (tradeSheetMountTimer) clearTimeout(tradeSheetMountTimer)
      tradeSheetMountTimer = setTimeout(() => {
        tradeSheetRenderContent.value = true
        tradeSheetMountTimer = undefined
        nextTick(() => {
          const pending = pendingMergeSplitDialog.value
          if (pending === 'merge') {
            pendingMergeSplitDialog.value = null
            mobileTradeComponentRef.value?.openMergeDialog?.()
          } else if (pending === 'split') {
            pendingMergeSplitDialog.value = null
            mobileTradeComponentRef.value?.openSplitDialog?.()
          }
        })
      }, 50)
    } else {
      pendingMergeSplitDialog.value = null
      if (tradeSheetMountTimer) {
        clearTimeout(tradeSheetMountTimer)
        tradeSheetMountTimer = undefined
      }
      tradeSheetUnmountTimer = setTimeout(() => {
        tradeSheetRenderContent.value = false
        tradeSheetUnmountTimer = undefined
      }, 350)
    }
  },
  { immediate: true },
)

// 当前市场的 marketID，用于筛选持仓和订单
const currentMarketId = computed(() => getMarketId(currentMarket.value))

// 持仓列表（仅当前市场）
const marketPositions = ref<PositionDisplayItem[]>([])
const positionLoading = ref(false)

/** 过滤掉份额为 0 的持仓项 */
const marketPositionsFiltered = computed(() =>
  marketPositions.value.filter((p) => {
    const n = parseFloat(p.shares?.replace(/[^0-9.]/g, '') ?? '')
    return Number.isFinite(n) && n > 0
  }),
)

/** 转为 TradeComponent 所需的 TradePositionItem[]；sharesNum 用 available（最大可卖份额），available > 0 表示有订单可卖；outcome 与 noLabel 匹配则为 No */
const tradePositionsForComponent = computed<TradePositionItem[]>(() => {
  const no = noLabel.value
  return marketPositionsFiltered.value.map((p) => ({
    id: p.id,
    outcomeWord: (p.outcome === no ? 'No' : 'Yes') as 'Yes' | 'No',
    shares: p.shares,
    sharesNum:
      p.availableSharesNum != null && Number.isFinite(p.availableSharesNum)
        ? p.availableSharesNum
        : parseFloat(p.shares?.replace(/[^0-9.]/g, '')) || undefined,
    locked: p.locked,
  }))
})

async function loadMarketPositions() {
  const marketID = currentMarketId.value
  if (!marketID) {
    marketPositions.value = []
    return
  }
  const headers = userStore.getAuthHeaders()
  if (!headers) {
    marketPositions.value = []
    return
  }
  const uid = userStore.user?.id ?? userStore.user?.ID
  const userID = uid != null ? Number(uid) : undefined
  if (!userID || !Number.isFinite(userID)) {
    marketPositions.value = []
    return
  }
  positionLoading.value = true
  try {
    const res = await getPositionList({ page: 1, pageSize: 50, marketID, userID }, { headers })
    if (res.code === 0 || res.code === 200) {
      marketPositions.value = res.data?.list?.map(mapPositionToDisplayItem) ?? []
    } else {
      marketPositions.value = []
    }
  } catch {
    marketPositions.value = []
  } finally {
    positionLoading.value = false
  }
}

// 限价未成交订单（仅当前市场）
const marketOpenOrders = ref<OpenOrderDisplayItem[]>([])
const openOrderLoading = ref(false)

async function loadMarketOpenOrders() {
  const marketID = currentMarketId.value
  if (!marketID) {
    marketOpenOrders.value = []
    return
  }
  const headers = userStore.getAuthHeaders()
  if (!headers) {
    marketOpenOrders.value = []
    return
  }
  const uid = userStore.user?.id ?? userStore.user?.ID
  const userID = uid != null ? Number(uid) : undefined
  if (!userID || !Number.isFinite(userID)) {
    marketOpenOrders.value = []
    return
  }
  openOrderLoading.value = true
  try {
    const res = await getOrderList(
      { page: 1, pageSize: 50, marketID, userID, status: OrderStatus.Live },
      { headers },
    )
    if (res.code === 0 || res.code === 200) {
      const list = res.data?.list ?? []
      const liveOnly = list.filter((o) => (o.status ?? 1) === OrderStatus.Live)
      marketOpenOrders.value = liveOnly.map(mapOrderToOpenOrderItem)
    } else {
      marketOpenOrders.value = []
    }
  } catch {
    marketOpenOrders.value = []
  } finally {
    openOrderLoading.value = false
  }
}

const cancelOrderLoading = ref(false)

async function cancelMarketOrder(ord: OpenOrderDisplayItem) {
  if (ord.fullyFilled) return
  const orderID = ord.orderID ?? 0
  const tokenID = ord.tokenID ?? ''
  const uid = userStore.user?.id ?? userStore.user?.ID
  const userID = uid != null ? Number(uid) : 0
  if (!Number.isFinite(userID) || userID <= 0 || !tokenID) return
  const headers = userStore.getAuthHeaders()
  if (!headers) return
  cancelOrderLoading.value = true
  try {
    const res = await apiCancelOrder({ orderID, tokenID, userID }, { headers })
    if (res.code === 0 || res.code === 200) {
      marketOpenOrders.value = marketOpenOrders.value.filter((o) => o.id !== ord.id)
      userStore.fetchUsdcBalance()
    }
  } finally {
    cancelOrderLoading.value = false
  }
}

// 持仓/限价 tab（订单簿上方）
const positionsOrdersTab = ref<'positions' | 'orders'>('positions')

// 切换持仓/限价 tab 或市场变化时加载
watch(
  [() => positionsOrdersTab.value, currentMarketId],
  ([tab]) => {
    if (tab === 'positions') loadMarketPositions()
    else if (tab === 'orders') loadMarketOpenOrders()
  },
  { immediate: true },
)

// Comments / Top Holders / Activity
const detailTab = ref('rules')
const activityMinAmount = ref<string>('0')

const minAmountOptions = computed(() => [
  { title: t('activity.any'), value: '0' },
  { title: '$1', value: '1' },
  { title: '$10', value: '10' },
  { title: '$100', value: '100' },
  { title: '$500', value: '500' },
])

interface ActivityItem {
  id: string
  user: string
  avatarClass: string
  avatarUrl?: string
  action: 'bought' | 'sold'
  side: 'Yes' | 'No'
  amount: number
  price: string
  total: string
  time: number
}
const activityList = ref<ActivityItem[]>([
  {
    id: 'a1',
    user: 'Scottp1887',
    avatarClass: 'avatar-gradient-1',
    action: 'bought',
    side: 'Yes',
    amount: 914,
    price: '32.8¢',
    total: '$300',
    time: Date.now() - 10 * 60 * 1000,
  },
  {
    id: 'a2',
    user: 'Outrageous-Budd...',
    avatarClass: 'avatar-gradient-2',
    action: 'sold',
    side: 'No',
    amount: 20,
    price: '70.0¢',
    total: '$14',
    time: Date.now() - 29 * 60 * 1000,
  },
  {
    id: 'a3',
    user: '0xbc7db42d5ea9a...',
    avatarClass: 'avatar-gradient-3',
    action: 'bought',
    side: 'Yes',
    amount: 5,
    price: '68.0¢',
    total: '$3.40',
    time: Date.now() - 60 * 60 * 1000,
  },
  {
    id: 'a4',
    user: 'FINNISH-FEMBOY',
    avatarClass: 'avatar-gradient-4',
    action: 'sold',
    side: 'No',
    amount: 57,
    price: '35.0¢',
    total: '$20',
    time: Date.now() - 2 * 60 * 60 * 1000,
  },
  {
    id: 'a5',
    user: 'CryptoWhale',
    avatarClass: 'avatar-gradient-1',
    action: 'bought',
    side: 'No',
    amount: 143,
    price: '28.0¢',
    total: '$40',
    time: Date.now() - 3 * 60 * 60 * 1000,
  },
  {
    id: 'a6',
    user: 'PolyTrader',
    avatarClass: 'avatar-gradient-2',
    action: 'sold',
    side: 'Yes',
    amount: 30,
    price: '72.0¢',
    total: '$21.60',
    time: Date.now() - 5 * 60 * 60 * 1000,
  },
])

const filteredActivity = computed(() => {
  const min = Number(activityMinAmount.value) || 0
  return activityList.value.filter((item) => {
    const totalNum = parseFloat(item.total.replace(/[$,]/g, ''))
    return totalNum >= min
  })
})

function formatTimeAgo(ts: number): string {
  const sec = Math.floor((Date.now() - ts) / 1000)
  if (sec < 60) return t('activity.justNow')
  if (sec < 3600) return t('activity.minutesAgo', { n: Math.floor(sec / 60) })
  if (sec < 86400) return t('activity.hoursAgo', { n: Math.floor(sec / 3600) })
  if (sec < 604800) return t('activity.daysAgo', { n: Math.floor(sec / 86400) })
  return t('activity.weeksAgo', { n: Math.floor(sec / 604800) })
}

// 时间粒度
const selectedTimeRange = ref('1D')
const timeRangesYesno = [
  { label: '1H', value: '1H' },
  { label: '6H', value: '6H' },
  { label: '1D', value: '1D' },
  { label: '1W', value: '1W' },
  { label: '1M', value: '1M' },
  { label: 'ALL', value: 'ALL' },
]
const timeRangesCrypto = [{ label: '30S', value: '30S' }, ...timeRangesYesno]
const timeRanges = computed(() =>
  chartMode.value === 'crypto' ? timeRangesCrypto : timeRangesYesno,
)

const chartContainerRef = ref<HTMLElement | null>(null)
const data = ref<[number, number][]>([])
/** Yes/No 折线图：接口返回的完整数据（time 已转 ms），用于分时筛选 */
const rawChartData = ref<ChartDataPoint[]>([])
const cryptoChartLoading = ref(false)
const chartYesNoLoading = ref(false)
let chartInstance: IChartApi | null = null
let chartSeries: ISeriesApi<'Line'> | null = null

const currentChance = computed(() => {
  const ev = eventDetail.value
  const market = ev?.markets?.[0]
  if (market?.outcomePrices?.[0] != null) {
    const p = parseFloat(String(market.outcomePrices[0]))
    if (Number.isFinite(p)) return Math.min(100, Math.max(0, Math.round(p * 100)))
  }
  const d = data.value
  const last = d.length > 0 ? d[d.length - 1] : undefined
  if (last != null) return last[1]
  const q = route.query.chance
  if (q != null) {
    const n = Number(q)
    if (Number.isFinite(n)) return Math.min(100, Math.max(0, Math.round(n)))
  }
  return 20
})

const lineColor = '#2563eb'

const MOBILE_BREAKPOINT = 600

function ensureChartSeries() {
  if (!chartInstance || !chartContainerRef.value) return
  if (chartSeries) {
    chartInstance.removeSeries(chartSeries)
    chartSeries = null
  }
  const isCrypto = chartMode.value === 'crypto'
  chartSeries = chartInstance.addSeries(LineSeries, {
    color: lineColor,
    lineWidth: 2,
    lineType: LineType.Curved,
    lastPriceAnimation: LastPriceAnimationMode.OnDataUpdate,
    crosshairMarkerVisible: true,
    lastValueVisible: true,
    priceFormat: isCrypto ? { type: 'price', precision: 2 } : { type: 'percent', precision: 1 },
  })
}

function setChartData(chartData: [number, number][]) {
  if (!chartSeries) return
  chartSeries.setData(toLwcData(chartData))
}

function initChart() {
  if (!chartContainerRef.value) return
  data.value = []
  const el = chartContainerRef.value
  chartInstance = createChart(el, {
    width: el.clientWidth,
    height: 320,
    layout: {
      background: { color: '#ffffff' },
      textColor: '#6b7280',
      fontFamily: 'inherit',
      fontSize: 9,
      attributionLogo: false,
    },
    grid: { vertLines: { color: '#f3f4f6' }, horzLines: { color: '#f3f4f6' } },
    rightPriceScale: { borderColor: '#e5e7eb', scaleMargins: { top: 0.1, bottom: 0.1 } },
    timeScale: { borderColor: '#e5e7eb', timeVisible: true, secondsVisible: false },
    crosshair: { vertLine: { labelVisible: false }, horzLine: { labelVisible: true } },
  })
  ensureChartSeries()
}

/** 从 GET /pmPriceHistory/getPmPriceHistoryPublic 拉取价格历史，market 传 YES 对应的 clobTokenId */
async function loadChartFromApi(marketParam: string, range: string): Promise<ChartDataPoint[]> {
  const ev = eventDetail.value
  const timeRange = getTimeRangeSeconds(
    range,
    ev ? { startDate: ev.startDate, endDate: ev.endDate } : undefined,
  )
  const res = await getPmPriceHistoryPublic({
    market: marketParam,
    page: 1,
    pageSize: 500,
    ...(timeRange && { startTs: timeRange.startTs, endTs: timeRange.endTs }),
  })
  const list = res.data?.list ?? []
  return priceHistoryToChartData(list)
}

const MINUTE_MS = 60 * 1000
let cryptoWsUnsubscribe: (() => void) | null = null

function applyCryptoRealtimePoint(point: [number, number]) {
  const list = [...data.value]
  const [ts, price] = point
  const range = selectedTimeRange.value
  let useIncrementalUpdate = false
  let updatePoint: [number, number] | null = null

  if (range === '30S') {
    const cutoff = Date.now() - 30 * 1000
    list.push([ts, price])
    const filtered = list.filter(([t]) => t >= cutoff).sort((a, b) => a[0] - b[0])
    data.value = filtered
    // 仅当未过滤掉任何点时可用 update 实现平滑动画
    useIncrementalUpdate = filtered.length === list.length
    if (useIncrementalUpdate) updatePoint = [ts, price]
  } else {
    const last = list[list.length - 1]
    const minuteStart = Math.floor(ts / MINUTE_MS) * MINUTE_MS
    const sameMinute = last && Math.floor(last[0] / MINUTE_MS) === Math.floor(ts / MINUTE_MS)
    const max =
      { '1H': 60, '6H': 360, '1D': 1440, '1W': 1680, '1M': 43200, ALL: 10080 }[range] ?? 60
    if (sameMinute) {
      list[list.length - 1] = [last![0], price]
      data.value = list.slice(-max)
      useIncrementalUpdate = true
      updatePoint = [last![0], price]
    } else {
      list.push([minuteStart, price])
      const sliced = list.slice(-max)
      data.value = sliced
      // 仅当未从头部裁剪时可用 update，否则需全量 setData
      useIncrementalUpdate = sliced.length === list.length
      if (useIncrementalUpdate) updatePoint = [minuteStart, price]
    }
  }

  if (chartSeries && useIncrementalUpdate && updatePoint) {
    chartSeries.update(toLwcPoint(updatePoint))
  } else {
    setChartData(data.value)
  }
}

async function updateChartData() {
  if (chartMode.value === 'crypto') {
    const sym = cryptoSymbol.value ?? 'btc'
    cryptoWsUnsubscribe?.()
    cryptoWsUnsubscribe = null
    cryptoChartLoading.value = true
    try {
      const res = await fetchCryptoChart({
        symbol: sym,
        range: selectedTimeRange.value,
      })
      data.value = (res.data ?? []) as [number, number][]
      ensureChartSeries()
      setChartData(data.value)
      cryptoWsUnsubscribe = subscribeCryptoRealtime(sym, applyCryptoRealtimePoint)
    } finally {
      cryptoChartLoading.value = false
    }
  } else {
    cryptoWsUnsubscribe?.()
    cryptoWsUnsubscribe = null
    chartYesNoLoading.value = true
    try {
      const yesTokenId = clobTokenIds.value[0]
      const points = yesTokenId ? await loadChartFromApi(yesTokenId, selectedTimeRange.value) : []
      rawChartData.value = points
      data.value = points
      ensureChartSeries()
      setChartData(data.value)
    } finally {
      chartYesNoLoading.value = false
    }
  }
}

function selectTimeRange(range: string) {
  selectedTimeRange.value = range
}

watch(selectedTimeRange, () => updateChartData())

// CLOB：当有 market 且存在 clobTokenIds 时连接（使用 Yes/No token ID）
const clobTokenIds = computed(() => {
  const m = currentMarket.value
  if (!m?.clobTokenIds?.length) return []
  const yes = getClobTokenId(m, 0)
  const no = getClobTokenId(m, 1)
  return [yes, no].filter((id): id is string => !!id)
})
watch(
  clobTokenIds,
  (tokenIds) => {
    if (tokenIds.length > 0) {
      const payload = tradeMarketPayload.value
      if (payload?.yesPrice != null) {
        clobLastPriceByToken.value = {
          ...clobLastPriceByToken.value,
          0: Math.round(payload.yesPrice * 100),
        }
      }
      connectClob(tokenIds)
    } else {
      disconnectClob()
    }
  },
  { immediate: true },
)

const handleResize = () => {
  if (!chartInstance || !chartContainerRef.value) return
  chartInstance.resize(chartContainerRef.value.clientWidth, 320)
}

watch(
  () => route.params.id,
  () => loadEventDetail(),
  { immediate: false },
)

// 监听语言切换，语言变化时重新加载数据
watch(
  () => localeStore.currentLocale,
  () => {
    loadEventDetail()
    loadMarketPositions()
  },
)

// 订阅 position_update，收到当前市场的推送时刷新持仓
const unsubscribePositionUpdate = userStore.onPositionUpdate((data) => {
  const marketID = data.marketID ?? (data as Record<string, unknown>).market_id
  if (marketID && String(marketID) === String(currentMarketId.value)) {
    loadMarketPositions()
  }
})

onMounted(() => {
  loadEventDetail().then(() => updateChartData())
  initChart()
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  unsubscribePositionUpdate()
  cryptoWsUnsubscribe?.()
  cryptoWsUnsubscribe = null
  window.removeEventListener('resize', handleResize)
  chartInstance?.remove()
  chartInstance = null
  chartSeries = null
  disconnectClob()
})
</script>

<style scoped>
.trade-detail-container {
  padding: 24px;
  padding-left: 24px;
  padding-right: 24px;
  min-height: 100vh;
  box-sizing: border-box;
}

.back-btn {
  margin-bottom: 16px;
}

.trade-detail-card {
  padding: 32px;
  background-color: #ffffff;
  border-radius: 12px;
  border: 1px solid #e7e7e7;
}

.trade-title {
  font-size: 1.5rem;
  font-weight: 600;
  color: #000000;
  margin-bottom: 24px;
  text-align: center;
}

.market-image-container {
  display: flex;
  justify-content: center;
  margin-bottom: 32px;
}

.market-image {
  border: 2px solid #e7e7e7;
}

.chance-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 32px;
}

.progress-bar {
  margin-bottom: 8px;
}

.chance-value {
  font-size: 24px;
  font-weight: 700;
  color: #000000;
}

.chance-label {
  font-size: 16px;
  font-weight: normal;
  color: #808080;
  margin-top: 8px;
}

.options-section {
  display: flex;
  gap: 16px;
  margin-bottom: 32px;
}

.option-yes {
  flex: 1;
  background-color: #b8e0b8;
  height: 56px;
  font-size: 18px;
}

.option-text-yes {
  font-size: 18px;
  font-weight: 600;
  color: #006600;
}

.option-no {
  flex: 1;
  background-color: #f0b8b8;
  height: 56px;
  font-size: 18px;
}

.option-text-no {
  font-size: 18px;
  font-weight: 600;
  color: #cc0000;
}

.market-info-section {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: 32px;
  padding: 24px;
  background-color: #f9f9f9;
  border-radius: 8px;
}

.info-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.info-label {
  font-size: 14px;
  font-weight: normal;
  color: #808080;
}

.info-value {
  font-size: 14px;
  font-weight: 500;
  color: #000000;
}

.description-section {
  margin-bottom: 32px;
}

.description-title {
  font-size: 16px;
  font-weight: 600;
  color: #000000;
  margin-bottom: 12px;
}

.description-text {
  font-size: 14px;
  font-weight: normal;
  color: #333333;
  line-height: 1.6;
}

.action-buttons {
  margin-top: 16px;
}

.action-btn {
  height: 56px;
  font-size: 16px;
  font-weight: 600;
}

/* Polymarket 样式分时图卡片（扁平化） */
.chart-card.polymarket-chart {
  margin-top: 0;
  padding: 20px 24px 16px;
  background-color: #ffffff;
  border: 1px solid #e7e7e7;
  border-radius: 12px;
  box-shadow: none;
}

.chart-header {
  margin-bottom: 16px;
}

.chart-title {
  font-size: 1.25rem;
  font-weight: 600;
  color: #111827;
  margin: 0 0 12px 0;
  line-height: 1.3;
}

.chart-error {
  font-size: 0.875rem;
  color: #dc2626;
  margin: 0 0 8px 0;
}

.chart-controls-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.past-btn {
  font-size: 13px;
  color: #6b7280;
  text-transform: none;
}

.date-pill {
  background-color: #111827 !important;
  color: #fff !important;
  font-size: 12px;
  font-weight: 500;
  text-transform: none;
}

.chart-chance {
  font-size: 1.5rem;
  font-weight: 700;
  color: #2563eb;
}

.chart-wrapper {
  position: relative;
  width: 100%;
  margin-bottom: 12px;
}

.chart-loading-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.6);
  z-index: 2;
}

.chart-mode-toggle .v-btn.active {
  background: rgb(var(--v-theme-primary));
  color: rgb(var(--v-theme-on-primary));
}

.chart-container {
  width: 100%;
  height: 320px;
  min-height: 260px;
}

.chart-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  padding-top: 12px;
  border-top: 1px solid #f3f4f6;
}

.chart-footer-left {
  font-size: 12px;
  color: #6b7280;
}

.chart-expires {
  margin-left: 4px;
}

.chart-time-ranges {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}

.time-range-btn {
  font-size: 12px;
  text-transform: none;
  min-width: 36px;
  color: #6b7280;
}

.time-range-btn.active {
  color: #111827;
  font-weight: 600;
}

.time-range-btn:hover {
  color: #111827;
}

/* Order Book Card Styles（扁平化） */
.positions-orders-card {
  margin-top: 16px;
  border: 1px solid #e5e7eb;
  overflow: hidden;
  box-shadow: none;
}

.positions-orders-tabs {
  border-bottom: 1px solid #e5e7eb;
}

.positions-orders-tabs :deep(.v-tab) {
  text-transform: none;
  font-size: 14px;
}

.positions-orders-window {
  overflow: visible;
}

.positions-orders-card .detail-pane {
  padding: 16px;
}

.order-book-card {
  margin-top: 16px;
  padding: 0;
  background-color: #ffffff;
  border: none;
  box-shadow: none;
}

/* 左右布局：左侧弹性，右侧固定；no-gutters 移除 v-col 默认 gutter，由容器 padding 统一控制左右边距 */
.trade-detail-row {
  display: flex;
  flex-wrap: wrap;
}

.chart-col {
  flex: 1 1 0%;
  min-width: 0;
}

.trade-col {
  margin-top: 32px;
}

.trade-sidebar {
  position: sticky;
  top: 24px;
  width: 400px;
  max-width: 100%;
  flex-shrink: 0;
}

@media (min-width: 960px) {
  .trade-detail-row {
    flex-wrap: nowrap;
    gap: 24px;
  }

  .chart-col {
    flex: 1 1 0% !important;
    min-width: 0;
    max-width: none !important;
  }

  .trade-col {
    flex: 0 0 400px !important;
    max-width: 400px !important;
    margin-top: 32px;
  }

  .trade-sidebar {
    width: 400px;
  }
}

@media (max-width: 959px) {
  .trade-sidebar {
    position: static;
    width: 100%;
  }

  /* 移动端显示底部栏时，预留底部空间避免遮挡列表 */
  .trade-detail-container {
    padding-bottom: 100px;
  }
}

/* Responsive adjustments */
@media (max-width: 599px) {
  .trade-detail-container {
    padding: 16px;
    padding-left: 16px;
    padding-right: 16px;
    /* 底部预留空间，避免固定 Yes/No 栏遮挡列表内容 */
    padding-bottom: 100px;
  }

  .trade-detail-card {
    padding: 24px;
  }

  .trade-title {
    font-size: 1.25rem;
  }

  .market-image-container {
    margin-bottom: 24px;
  }

  .chance-section {
    margin-bottom: 24px;
  }

  .options-section {
    margin-bottom: 24px;
  }

  .market-info-section {
    margin-bottom: 24px;
    padding: 16px;
  }

  .description-section {
    margin-bottom: 24px;
  }

  .chart-container {
    height: 260px;
  }

  .chart-card.polymarket-chart {
    padding: 16px;
  }

  .chart-title {
    font-size: 1.125rem;
  }
}

/* Responsive order book adjustments */
@media (max-width: 600px) {
  .order-book-card {
    margin-top: 16px;
  }
}

/* Comments / Top Holders / Activity（扁平化） */
.activity-card {
  margin-top: 16px;
  border: 1px solid #e5e7eb;
  overflow: hidden;
  box-shadow: none;
}

.detail-tabs {
  border-bottom: 1px solid #e5e7eb;
}

.detail-tabs :deep(.v-tab) {
  text-transform: none;
  font-size: 14px;
}

.detail-window {
  overflow: visible;
}

.detail-pane {
  padding: 16px;
}

.placeholder-pane {
  color: #6b7280;
  font-size: 14px;
  padding: 24px 0;
}

.rules-pane {
  padding: 4px 0;
}

.rules-section {
  margin-bottom: 16px;
}

.rules-section:last-child {
  margin-bottom: 0;
}

.rules-title {
  font-size: 13px;
  font-weight: 600;
  color: #6b7280;
  margin: 0 0 8px 0;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.rules-text {
  font-size: 14px;
  color: #374151;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

.rules-link {
  font-size: 14px;
  color: #2563eb;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.rules-link:hover {
  text-decoration: underline;
}

/* 持仓 / 限价订单列表 */
.positions-list,
.orders-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.position-row-item,
.order-row-item {
  padding: 12px 0;
  border-bottom: 1px solid #f0f0f0;
}

.position-row-item:last-child,
.order-row-item:last-child {
  border-bottom: none;
}

.position-row-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}

.position-row-icon {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  font-weight: 700;
  font-size: 16px;
  color: #fff;
}

.position-row-icon.pill-yes {
  background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
}

.position-row-icon.pill-down {
  background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
}

.position-row-icon-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

.position-row-icon-char {
  line-height: 1;
}

.position-row-title {
  font-size: 14px;
  font-weight: 500;
  color: #111827;
  line-height: 1.3;
  flex: 1;
  min-width: 0;
}

.position-row-main,
.order-row-main {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.position-sell-btn {
  margin-left: auto;
}

.position-lock-badge {
  font-size: 11px;
  color: #b45309;
  background: #fef3c7;
  padding: 2px 6px;
  border-radius: 4px;
  font-weight: 500;
}

.position-outcome-pill,
.order-side-pill {
  font-size: 12px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 4px;
}

.position-outcome-pill.pill-yes,
.order-side-pill.side-yes {
  background: #dcfce7;
  color: #166534;
}

.position-outcome-pill.pill-down,
.order-side-pill.side-no {
  background: #fee2e2;
  color: #991b1b;
}

.position-shares,
.position-value,
.order-price,
.order-filled,
.order-total {
  font-size: 14px;
  color: #374151;
}

.position-row-meta {
  font-size: 13px;
  color: #6b7280;
  margin-top: 4px;
}

.order-row-item {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
}

.order-row-actions {
  flex-shrink: 0;
}

.activity-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
  flex-wrap: wrap;
  gap: 12px;
}

.min-amount-select {
  max-width: 140px;
  font-size: 14px;
}

.min-amount-select :deep(.v-field) {
  font-size: 14px;
}

.live-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: #6b7280;
}

.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: #dc2626;
  animation: live-pulse 1.5s ease-in-out infinite;
}

@keyframes live-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
}

.activity-list {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.activity-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid #f3f4f6;
  font-size: 14px;
}

.activity-item:last-child {
  border-bottom: none;
}

.activity-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  flex-shrink: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar-gradient-1 {
  background: linear-gradient(135deg, #10b981 0%, #8b5cf6 100%);
}

.avatar-gradient-2 {
  background: linear-gradient(135deg, #10b981 0%, #3b82f6 100%);
}

.avatar-gradient-3 {
  background: linear-gradient(135deg, #f59e0b 0%, #eab308 100%);
}

.avatar-gradient-4 {
  background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
}

.activity-body {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
}

.activity-user {
  font-weight: 500;
  color: #111827;
}

.activity-action {
  color: #6b7280;
  margin-left: 2px;
}

.activity-amount {
  font-weight: 600;
  margin-left: 2px;
}

.amount-yes {
  color: #059669;
}

.amount-no {
  color: #dc2626;
}

.activity-price {
  color: #6b7280;
  margin-left: 2px;
}

.activity-total {
  color: #6b7280;
  margin-left: 2px;
}

.activity-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.activity-time {
  font-size: 13px;
  color: #9ca3af;
}

.activity-link {
  color: #9ca3af;
  display: inline-flex;
  line-height: 1;
}

.activity-link:hover {
  color: #6b7280;
}

/* 移动端底部交易栏（与 EventMarkets 一致） */
.mobile-trade-bar-spacer {
  /* 预留空间避免底部栏遮挡列表内容，需覆盖 bar 高度 + safe-area */
  height: 100px;
  min-height: 100px;
}

.mobile-trade-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  align-items: stretch;
  gap: 10px;
  width: 100%;
  padding: 12px 16px;
  padding-bottom: max(12px, env(safe-area-inset-bottom));
  background: #fff;
  border-top: 1px solid #eee;
}

.mobile-bar-btn {
  border: none;
  border-radius: 6px;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  flex-shrink: 0;
  text-transform: none;
}

.mobile-bar-yes {
  flex: 1;
  min-width: 0;
  background: #b8e0b8 !important;
  color: #006600 !important;
  height: 46px;
}

.mobile-bar-no {
  flex: 1;
  min-width: 0;
  background: #f0b8b8 !important;
  color: #cc0000 !important;
  height: 46px;
}

.mobile-bar-more-btn {
  flex-shrink: 0;
  width: 46px;
  height: 46px;
  min-width: 46px;
  min-height: 46px;
  padding: 0 !important;
  border-radius: 50%;
  background: #e5e7eb !important;
  color: #6b7280;
  align-self: center;
}
</style>
