<script setup lang="ts">
import { Check, Close, Picture, Refresh, Search, WarningFilled } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'

import { fetchAdminFileBlob } from '@/shared/api/files'
import { adjustListingReviewPrice, approveListing, fetchPendingReviewListings, rejectListing, type Listing } from '@/features/listings'
import {
  assetRegions,
  formatHafCoinM,
  getCoinWan,
  getListingConsumablePrice,
  getListingResources,
  getResourceQuantity,
  getSkinNames,
  readAssetNumber,
  readAssetString,
} from '@/utils/listingDisplay'
import { formatDateTime } from '@/utils/time'

type RiskLevel = 'danger' | 'warning' | 'info'

interface RiskItem {
  label: string
  level: RiskLevel
}

const defaultScreenshotURL = '/api/listings/default-upload-screenshot'
const rejectReasonOptions = ['默认截图，需补充真实截图', '账号资产信息不完整', '价格或押金异常', '封禁记录需补充说明', '联系方式异常']

const loading = ref(false)
const submitting = ref(false)
const listings = ref<Listing[]>([])
const selectedID = ref<number | null>(null)
const activeListing = ref<Listing | null>(null)
const evidenceListing = ref<Listing | null>(null)
const rejectReason = ref('')
const priceAdjustListing = ref<Listing | null>(null)
const adjustingPrice = ref(false)
const priceAdjustMode = ref<'ratio' | 'price'>('ratio')
const priceAdjustForm = reactive({
  buyer_ratio: 0,
  buyer_total_price: 0,
  reason: '',
})
const filters = reactive({
  keyword: '',
  risk: 'all',
})
const previewURLs = reactive<Record<string, string>>({})
const createdObjectURLs = new Set<string>()

const selectedListing = computed(() => listings.value.find((item) => item.id === selectedID.value) || listings.value[0] || null)
const filteredListings = computed(() => {
  const keyword = filters.keyword.trim().toLowerCase()
  return listings.value.filter((item) => {
    if (filters.risk === 'external' && !isExternalUpload(item)) return false
    if (filters.risk === 'defaultImage' && !hasDefaultScreenshot(item)) return false
    if (filters.risk === 'ban' && !hasBanRecord(item)) return false
    if (!keyword) return true
    return reviewSearchText(item).includes(keyword)
  })
})
const activeRisks = computed(() => (selectedListing.value ? riskItems(selectedListing.value) : []))
const selectedResources = computed(() => (selectedListing.value ? getListingResources(selectedListing.value) : []))
const selectedSkins = computed(() => (selectedListing.value ? getSkinNames(selectedListing.value) : []))
const priceAdjustPreview = computed(() => (priceAdjustListing.value ? calculatePriceAdjustPreview(priceAdjustListing.value) : null))

watch(
  () => selectedListing.value,
  async (listing) => {
    if (!listing) return
    selectedID.value = listing.id
    await nextTick()
    loadScreenshotPreviews(listing)
  },
  { immediate: true },
)

watch(priceAdjustMode, (mode, previousMode) => {
  if (!priceAdjustListing.value || !previousMode || mode === previousMode) return
  const preview = calculatePriceAdjustPreview(priceAdjustListing.value, previousMode)
  if (mode === 'price') {
    priceAdjustForm.buyer_total_price = preview.buyerTotalPrice || buyerTotalPrice(priceAdjustListing.value)
  } else {
    priceAdjustForm.buyer_ratio = preview.buyerRatio || buyerRatio(priceAdjustListing.value)
  }
})

onMounted(loadListings)
onBeforeUnmount(() => {
  createdObjectURLs.forEach((url) => URL.revokeObjectURL(url))
})

async function loadListings() {
  loading.value = true
  try {
    listings.value = await fetchPendingReviewListings()
    if (!selectedID.value || !listings.value.some((item) => item.id === selectedID.value)) {
      selectedID.value = listings.value[0]?.id || null
    }
  } finally {
    loading.value = false
  }
}

function selectListing(row: Listing) {
  selectedID.value = row.id
}

function replaceListing(next: Listing) {
  const index = listings.value.findIndex((item) => item.id === next.id)
  if (index >= 0) {
    listings.value.splice(index, 1, next)
  } else {
    listings.value.unshift(next)
  }
  selectedID.value = next.id
}

async function handleApprove(row: Listing) {
  try {
    await ElMessageBox.confirm(`确认通过「${row.title}」并上架？`, '审核通过确认', {
      confirmButtonText: '通过并上架',
      cancelButtonText: '取消',
      type: 'warning',
    })
  } catch {
    return
  }
  submitting.value = true
  try {
    await approveListing(row.id)
    ElMessage.success('审核通过，商品已上架')
    await loadListings()
  } catch (error) {
    ElMessage.error(readError(error, '审核失败'))
  } finally {
    submitting.value = false
  }
}

function openReject(row: Listing) {
  activeListing.value = row
  rejectReason.value = row.review_reason || ''
}

function openPriceAdjust(row: Listing) {
  priceAdjustListing.value = row
  priceAdjustMode.value = 'ratio'
  priceAdjustForm.buyer_ratio = buyerRatio(row)
  priceAdjustForm.buyer_total_price = buyerTotalPrice(row)
  priceAdjustForm.reason = ''
}

async function handleSavePriceAdjust() {
  if (!priceAdjustListing.value) return
  const payload =
    priceAdjustMode.value === 'ratio'
      ? { buyer_ratio: Number(priceAdjustForm.buyer_ratio || 0), reason: priceAdjustForm.reason.trim() }
      : { buyer_total_price: Number(priceAdjustForm.buyer_total_price || 0), reason: priceAdjustForm.reason.trim() }
  if ((payload.buyer_ratio || payload.buyer_total_price || 0) <= 0) {
    ElMessage.warning('请填写有效的加价后比例或价格')
    return
  }
  adjustingPrice.value = true
  try {
    const updated = await adjustListingReviewPrice(priceAdjustListing.value.id, payload)
    replaceListing(updated)
    priceAdjustListing.value = null
    ElMessage.success('价格已更新')
  } catch (error) {
    ElMessage.error(readError(error, '价格调整失败'))
  } finally {
    adjustingPrice.value = false
  }
}

function appendRejectReason(reason: string) {
  const current = rejectReason.value.trim()
  if (!current) {
    rejectReason.value = reason
    return
  }
  if (!current.includes(reason)) {
    rejectReason.value = `${current}；${reason}`
  }
}

async function handleReject() {
  if (!activeListing.value) return
  const reason = rejectReason.value.trim()
  if (!reason) {
    ElMessage.warning('请填写拒绝原因')
    return
  }
  submitting.value = true
  try {
    await rejectListing(activeListing.value.id, reason)
    ElMessage.success('已拒绝发布并通知号主')
    activeListing.value = null
    rejectReason.value = ''
    await loadListings()
  } catch (error) {
    ElMessage.error(readError(error, '拒绝失败'))
  } finally {
    submitting.value = false
  }
}

function openEvidence(row: Listing) {
  evidenceListing.value = row
  loadScreenshotPreviews(row)
}

function money(value: number) {
  return `¥${Math.round(Number(value || 0))}`
}

function quantity(value: number) {
  const rounded = Math.round(Number(value || 0) * 10) / 10
  return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(1)
}

function assetText(row: Listing, key: string) {
  return readAssetString(row, key) || '-'
}

function assetNumberText(row: Listing, key: string) {
  const value = readAssetNumber(row, key)
  return value > 0 ? quantity(value) : '-'
}

function priceBreakdown(row: Listing) {
  const breakdown = row.asset_summary?.price_breakdown
  return typeof breakdown === 'object' && breakdown !== null ? (breakdown as Record<string, unknown>) : {}
}

function breakdownNumber(row: Listing, key: string) {
  const value = priceBreakdown(row)[key]
  if (typeof value === 'number') return value
  if (typeof value === 'string' && value.trim()) {
    const parsed = Number(value)
    return Number.isFinite(parsed) ? parsed : 0
  }
  return 0
}

function ratioText(value: number) {
  return value > 0 ? `1:${quantity(value)}` : '-'
}

function previewMoney(value: number) {
  return value > 0 ? money(value) : '--'
}

function roundPreviewMoney(value: number) {
  if (!Number.isFinite(value) || value <= 0) return 0
  return Math.round(value * 10) / 10
}

function roundPreviewRatio(value: number) {
  if (!Number.isFinite(value) || value <= 0) return 0
  return Math.round(value * 10) / 10
}

function sellerTotalPrice(row: Listing) {
  const value = breakdownNumber(row, 'seller_total_price')
  if (value > 0) return value
  const fallback = Number(row.price || 0) - getListingConsumablePrice(row)
  return fallback > 0 ? fallback : Number(row.price || 0)
}

function sellerCoinBasePrice(row: Listing) {
  const value = breakdownNumber(row, 'seller_coin_base_price')
  if (value > 0) return value
  return Math.max(0, sellerTotalPrice(row) - getListingConsumablePrice(row))
}

function sellerRatio(row: Listing) {
  const value = breakdownNumber(row, 'seller_ratio') || breakdownNumber(row, 'seller_reference_ratio')
  if (value > 0) return value
  const base = sellerCoinBasePrice(row)
  return base > 0 ? getCoinWan(row) / base : 0
}

function buyerTotalPrice(row: Listing) {
  const value = breakdownNumber(row, 'buyer_total_price')
  return value > 0 ? value : Number(row.price || 0)
}

function buyerCoinBasePrice(row: Listing) {
  const value = breakdownNumber(row, 'buyer_coin_base_price')
  if (value > 0) return value
  return Math.max(0, buyerTotalPrice(row) - getListingConsumablePrice(row))
}

function buyerRatio(row: Listing) {
  const value = breakdownNumber(row, 'buyer_ratio')
  if (value > 0) return value
  const base = buyerCoinBasePrice(row)
  return base > 0 ? getCoinWan(row) / base : 0
}

function calculatePriceAdjustPreview(row: Listing, mode: 'ratio' | 'price' = priceAdjustMode.value) {
  const consumablePrice = getListingConsumablePrice(row)
  const coinWan = getCoinWan(row)
  if (mode === 'price') {
    const buyerTotalPrice = roundPreviewMoney(Number(priceAdjustForm.buyer_total_price || 0))
    const buyerCoinBasePrice = roundPreviewMoney(buyerTotalPrice - consumablePrice)
    const buyerRatio = buyerCoinBasePrice > 0 ? roundPreviewRatio(coinWan / buyerCoinBasePrice) : 0
    return { buyerCoinBasePrice, buyerTotalPrice, buyerRatio }
  }
  const buyerRatioInput = Number(priceAdjustForm.buyer_ratio || 0)
  const buyerCoinBasePrice = buyerRatioInput > 0 ? roundPreviewMoney(coinWan / buyerRatioInput) : 0
  const buyerTotalPrice = buyerCoinBasePrice > 0 ? roundPreviewMoney(buyerCoinBasePrice + consumablePrice) : 0
  const buyerRatio = buyerCoinBasePrice > 0 ? roundPreviewRatio(coinWan / buyerCoinBasePrice) : 0
  return { buyerCoinBasePrice, buyerTotalPrice, buyerRatio }
}

function platformMarkupAmount(row: Listing) {
  const value = breakdownNumber(row, 'platform_markup_amount')
  if (value > 0) return value
  return Math.max(0, buyerTotalPrice(row) - sellerTotalPrice(row))
}

function priceRuleType(row: Listing) {
  const value = priceBreakdown(row).platform_rule_type
  return typeof value === 'string' && value ? value : 'none'
}

function priceRuleLabel(row: Listing) {
  const labels: Record<string, string> = {
    fixed_markup: '固定加价',
    ratio_subtract: '比例加价',
    external_upload: '外部上传价',
    admin_adjusted: '后台已调整',
    none: '无加价',
  }
  return labels[priceRuleType(row)] || priceRuleType(row)
}

function breakdownText(row: Listing, key: string) {
  const value = priceBreakdown(row)[key]
  return typeof value === 'string' && value.trim() ? value.trim() : ''
}

function adminAdjustReason(row: Listing) {
  return breakdownText(row, 'admin_adjust_reason')
}

function priceRuleText(row: Listing) {
  const markup = platformMarkupAmount(row)
  if (priceRuleType(row) === 'admin_adjusted') {
    const reason = adminAdjustReason(row)
    return reason ? `原因：${reason}` : `${priceRuleLabel(row)} · 人工确认`
  }
  if (priceRuleType(row) === 'ratio_subtract') {
    return `${priceRuleLabel(row)} · ${ratioText(sellerRatio(row))} → ${ratioText(buyerRatio(row))}`
  }
  if (markup > 0) return `${priceRuleLabel(row)} · +${money(markup)}`
  return priceRuleLabel(row)
}

function dailyLossText(row: Listing) {
  const value = readAssetNumber(row, 'daily_loss_m')
  return value > 0 ? `${quantity(value)}M` : '未上传'
}

function uploadMeta(row: Listing) {
  const meta = row.asset_summary?.import_meta
  return typeof meta === 'object' && meta !== null ? (meta as Record<string, unknown>) : {}
}

function uploaderName(row: Listing) {
  const value = uploadMeta(row).uploader_name
  return typeof value === 'string' && value.trim() ? value : '-'
}

function contactPhone(row: Listing) {
  const value = uploadMeta(row).contact_phone
  return typeof value === 'string' && value.trim() ? value : '-'
}

function ownerOnlineText(row: Listing) {
  const text = row.asset_summary?.online_time_text
  if (typeof text === 'string' && text.trim()) return text
  const onlineTime = row.asset_summary?.online_time
  if (typeof onlineTime !== 'object' || onlineTime === null) return '-'
  const start = (onlineTime as Record<string, unknown>).start
  const end = (onlineTime as Record<string, unknown>).end
  if (typeof start === 'string' && typeof end === 'string' && start && end) return `${start}-${end}`
  return '-'
}

function commonRegionText(row: Listing) {
  const regions = assetRegions(row)
  return regions.length ? regions.join('、') : '-'
}

function banRecordText(row: Listing) {
  return assetText(row, 'ban_record')
}

function hasBanRecord(row: Listing) {
  const value = banRecordText(row)
  return value !== '-' && !value.includes('无')
}

function isExternalUpload(row: Listing) {
  return uploaderName(row) !== '-'
}

function hasDefaultScreenshot(row: Listing) {
  return row.screenshot_urls?.some((url) => isDefaultScreenshot(url)) || false
}

function isDefaultScreenshot(url: string) {
  return url.includes(defaultScreenshotURL)
}

function riskItems(row: Listing): RiskItem[] {
  const items: RiskItem[] = []
  if (isExternalUpload(row)) items.push({ label: `外部上传：${uploaderName(row)}`, level: 'info' })
  if (!row.screenshot_urls?.length) items.push({ label: '没有账号截图', level: 'danger' })
  if (hasDefaultScreenshot(row)) items.push({ label: '使用默认截图', level: 'warning' })
  if (hasBanRecord(row)) items.push({ label: `封禁记录：${banRecordText(row)}`, level: 'danger' })
  if (getListingConsumablePrice(row) > 0 && Number(row.deposit_amount || 0) <= getListingConsumablePrice(row)) {
    items.push({ label: '押金不高于消耗品价值', level: 'danger' })
  }
  if (readAssetNumber(row, 'daily_loss_m') <= 0) items.push({ label: '缺少每日损耗', level: 'warning' })
  if (readAssetNumber(row, 'fire_level') <= 40) items.push({ label: '烽火等级接近下限', level: 'warning' })
  if (!readAssetString(row, 'season_insurance')) items.push({ label: '缺少保险格数', level: 'warning' })
  if (!items.length) items.push({ label: '未发现明显风险', level: 'info' })
  return items
}

function riskTagType(level: RiskLevel) {
  if (level === 'danger') return 'danger'
  if (level === 'warning') return 'warning'
  return 'info'
}

function reviewSearchText(row: Listing) {
  return [
    row.title,
    row.owner_phone,
    row.owner_nickname,
    row.owner_id,
    row.server_region,
    row.login_platform,
    row.rank_level,
    uploaderName(row),
    getSkinNames(row).join(' '),
  ]
    .join(' ')
    .toLowerCase()
}

function extractObjectKey(url: string) {
  try {
    const parsed = new URL(url, window.location.origin)
    return parsed.searchParams.get('key') || ''
  } catch {
    return ''
  }
}

async function loadScreenshotPreviews(row: Listing) {
  for (const url of row.screenshot_urls || []) {
    if (previewURLs[url]) continue
    if (isDefaultScreenshot(url) || !extractObjectKey(url)) {
      previewURLs[url] = url
      continue
    }
    try {
      const blob = await fetchAdminFileBlob(extractObjectKey(url))
      const objectURL = URL.createObjectURL(blob)
      createdObjectURLs.add(objectURL)
      previewURLs[url] = objectURL
    } catch {
      previewURLs[url] = url
    }
  }
}

async function openScreenshot(url: string) {
  if (isDefaultScreenshot(url)) {
    window.open(url, '_blank')
    return
  }
  try {
    const key = extractObjectKey(url)
    if (!key) {
      window.open(url, '_blank')
      return
    }
    const blob = await fetchAdminFileBlob(key)
    window.open(URL.createObjectURL(blob), '_blank')
  } catch {
    window.open(url, '_blank')
  }
}

function readError(error: unknown, fallback: string) {
  if (typeof error === 'object' && error && 'response' in error) {
    const response = (error as { response?: { data?: { message?: string } } }).response
    return response?.data?.message || fallback
  }
  return fallback
}
</script>

<template>
  <section class="page review-page">
    <div class="page-header-row review-header">
      <div class="page-header">
        <p class="eyebrow">Review</p>
        <h1>商品审核</h1>
        <p>集中核对账号资产、上传来源、价格、押金和截图风险。</p>
      </div>
      <div class="toolbar-actions">
        <el-button :icon="Refresh" :loading="loading" @click="loadListings">刷新</el-button>
      </div>
    </div>

    <div class="review-summary">
      <button class="summary-card" :class="{ active: filters.risk === 'all' }" @click="filters.risk = 'all'">
        <span>待审核</span>
        <strong>{{ listings.length }}</strong>
      </button>
      <button class="summary-card" :class="{ active: filters.risk === 'external' }" @click="filters.risk = 'external'">
        <span>外部上传</span>
        <strong>{{ listings.filter(isExternalUpload).length }}</strong>
      </button>
      <button class="summary-card" :class="{ active: filters.risk === 'defaultImage' }" @click="filters.risk = 'defaultImage'">
        <span>默认截图</span>
        <strong>{{ listings.filter(hasDefaultScreenshot).length }}</strong>
      </button>
      <button class="summary-card" :class="{ active: filters.risk === 'ban' }" @click="filters.risk = 'ban'">
        <span>封禁风险</span>
        <strong>{{ listings.filter(hasBanRecord).length }}</strong>
      </button>
    </div>

    <div class="review-workbench">
      <aside class="review-queue">
        <div class="queue-toolbar">
          <el-input v-model="filters.keyword" :prefix-icon="Search" clearable placeholder="搜索标题、客服、段位、皮肤" />
          <el-select v-model="filters.risk" placeholder="风险筛选">
            <el-option label="全部待审" value="all" />
            <el-option label="外部上传" value="external" />
            <el-option label="默认截图" value="defaultImage" />
            <el-option label="有封禁记录" value="ban" />
          </el-select>
        </div>

        <div v-loading="loading" class="queue-list">
          <button
            v-for="item in filteredListings"
            :key="item.id"
            class="queue-item"
            :class="{ active: selectedListing?.id === item.id }"
            type="button"
            @click="selectListing(item)"
          >
            <div class="queue-title-row">
              <strong>{{ item.title }}</strong>
              <span>{{ formatHafCoinM(getCoinWan(item)) }}</span>
            </div>
            <div class="queue-meta">
              <span>{{ item.rank_level || '-' }}</span>
              <span>{{ assetText(item, 'season_insurance') }}</span>
              <span>{{ assetText(item, 'stamina_level') }}/{{ assetText(item, 'load_level') }}</span>
              <span>KD {{ assetNumberText(item, 'secret_kd') }}</span>
            </div>
            <div class="queue-footer">
              <span>{{ money(item.price) }} / 押 {{ money(item.deposit_amount) }}</span>
              <el-tag v-if="isExternalUpload(item)" size="small" type="info">{{ uploaderName(item) }}</el-tag>
              <el-tag v-if="hasDefaultScreenshot(item)" size="small" type="warning">默认图</el-tag>
            </div>
          </button>
          <el-empty v-if="!loading && !filteredListings.length" description="暂无符合条件的待审核商品" />
        </div>
      </aside>

      <main v-if="selectedListing" class="review-detail">
        <section class="review-hero-panel">
          <div class="hero-title">
            <div>
              <p>商品 #{{ selectedListing.id }}</p>
              <h2>{{ selectedListing.title }}</h2>
            </div>
            <div class="hero-actions">
              <RouterLink :to="`/admin/listings/${selectedListing.id}`">
                <el-button>详情页</el-button>
              </RouterLink>
              <el-button :icon="Close" type="danger" :loading="submitting" @click="openReject(selectedListing)">拒绝</el-button>
              <el-button :icon="Check" type="primary" :loading="submitting" @click="handleApprove(selectedListing)">通过</el-button>
            </div>
          </div>
          <div class="risk-strip">
            <el-tag v-for="risk in activeRisks" :key="risk.label" :type="riskTagType(risk.level)" effect="light">
              {{ risk.label }}
            </el-tag>
          </div>
        </section>

        <section class="review-panel price-review-panel">
          <div class="panel-title price-panel-title">
            <div>
              <h3>价格审核</h3>
              <p>核对卖家提交价格、平台加价规则和最终买家展示价。</p>
            </div>
            <el-button type="primary" plain @click="openPriceAdjust(selectedListing)">调整价格</el-button>
          </div>
          <div class="price-decision-grid">
            <div class="price-decision-card seller">
              <span>卖家发布</span>
              <strong>{{ money(sellerTotalPrice(selectedListing)) }}</strong>
              <p>发布比例 {{ ratioText(sellerRatio(selectedListing)) }}</p>
              <small>纯币 {{ money(sellerCoinBasePrice(selectedListing)) }} / 物品 {{ money(getListingConsumablePrice(selectedListing)) }}</small>
            </div>
            <div class="price-decision-card rule">
              <span>当前加价规则</span>
              <strong>{{ priceRuleLabel(selectedListing) }}</strong>
              <p>{{ priceRuleText(selectedListing) }}</p>
              <small>平台加价 {{ money(platformMarkupAmount(selectedListing)) }}</small>
            </div>
            <div class="price-decision-card buyer">
              <span>加价后展示</span>
              <strong>{{ money(buyerTotalPrice(selectedListing)) }}</strong>
              <p>加价后比例 {{ ratioText(buyerRatio(selectedListing)) }}</p>
              <small>纯币 {{ money(buyerCoinBasePrice(selectedListing)) }} / 总价含物品</small>
            </div>
            <div class="price-decision-card deposit">
              <span>押金与损耗</span>
              <strong>{{ money(selectedListing.deposit_amount) }}</strong>
              <p>每日损耗 {{ dailyLossText(selectedListing) }}</p>
              <small>哈夫币 {{ formatHafCoinM(getCoinWan(selectedListing)) }}</small>
            </div>
          </div>
        </section>

        <section class="review-grid">
          <div class="review-panel">
            <div class="panel-title">
              <h3>账号属性</h3>
            </div>
            <dl class="detail-list">
              <div><dt>上号方式</dt><dd>{{ selectedListing.login_platform || '-' }}</dd></div>
              <div><dt>区服</dt><dd>{{ selectedListing.server_region || '-' }}</dd></div>
              <div><dt>段位</dt><dd>{{ selectedListing.rank_level || '-' }}</dd></div>
              <div><dt>烽火等级</dt><dd>{{ assetNumberText(selectedListing, 'fire_level') }}</dd></div>
              <div><dt>保险格数</dt><dd>{{ assetText(selectedListing, 'season_insurance') }}</dd></div>
              <div><dt>绝密KD</dt><dd>{{ assetNumberText(selectedListing, 'secret_kd') }}</dd></div>
              <div><dt>体力/负重</dt><dd>{{ assetText(selectedListing, 'stamina_level') }} / {{ assetText(selectedListing, 'load_level') }}</dd></div>
              <div><dt>封禁记录</dt><dd>{{ banRecordText(selectedListing) }}</dd></div>
            </dl>
          </div>

          <div class="review-panel">
            <div class="panel-title">
              <h3>上传信息</h3>
              <el-tag v-if="isExternalUpload(selectedListing)" size="small" type="info">外部上传</el-tag>
            </div>
            <dl class="detail-list">
              <div><dt>上传人</dt><dd>{{ uploaderName(selectedListing) }}</dd></div>
              <div><dt>号主</dt><dd>{{ selectedListing.owner_phone || selectedListing.owner_nickname || selectedListing.owner_id }}</dd></div>
              <div><dt>联系电话</dt><dd>{{ contactPhone(selectedListing) }}</dd></div>
              <div><dt>常用地区</dt><dd>{{ commonRegionText(selectedListing) }}</dd></div>
              <div><dt>在线时间</dt><dd>{{ ownerOnlineText(selectedListing) }}</dd></div>
              <div><dt>提交时间</dt><dd>{{ formatDateTime(selectedListing.updated_at) }}</dd></div>
            </dl>
          </div>
        </section>

        <section class="review-panel">
          <div class="panel-title">
            <h3>库存资产</h3>
            <span>额外消耗品约 {{ money(getListingConsumablePrice(selectedListing)) }}</span>
          </div>
          <div class="inventory-grid">
            <div><span>AWM子弹</span><strong>{{ getResourceQuantity(selectedListing, 'awmAmmo') }}</strong></div>
            <div><span>6头</span><strong>{{ getResourceQuantity(selectedListing, 'helmet6') }}</strong></div>
            <div><span>6甲</span><strong>{{ getResourceQuantity(selectedListing, 'armor6') }}</strong></div>
          </div>
          <div v-if="selectedResources.length" class="resource-table">
            <div v-for="resource in selectedResources" :key="resource.key">
              <span>{{ resource.label }}</span>
              <strong>{{ resource.quantity }}</strong>
              <em>{{ resource.mode }} · {{ resource.price }}</em>
            </div>
          </div>
          <div class="skin-list">
            <el-tag v-for="skin in selectedSkins" :key="skin" type="success" effect="plain">{{ skin }}</el-tag>
            <span v-if="!selectedSkins.length">暂无皮肤数据</span>
          </div>
        </section>

        <section class="review-panel">
          <div class="panel-title">
            <h3>账号截图</h3>
            <el-button :icon="Picture" size="small" @click="openEvidence(selectedListing)">查看全部</el-button>
          </div>
          <div v-if="selectedListing.screenshot_urls?.length" class="screenshot-grid">
            <button v-for="url in selectedListing.screenshot_urls" :key="url" type="button" class="screenshot-tile" @click="openScreenshot(url)">
              <img :src="previewURLs[url] || url" alt="账号截图" />
              <span v-if="isDefaultScreenshot(url)">默认图片</span>
            </button>
          </div>
          <div v-else class="empty-warning">
            <el-icon><WarningFilled /></el-icon>
            <span>暂无截图</span>
          </div>
        </section>
      </main>

      <main v-else class="review-detail empty-detail">
        <el-empty description="暂无待审核商品" />
      </main>
    </div>

    <el-dialog :model-value="!!activeListing" title="拒绝发布" width="620px" @update:model-value="activeListing = null">
      <div v-if="activeListing" class="dialog-body reject-dialog">
        <p><strong>{{ activeListing.title }}</strong></p>
        <div class="reject-reasons">
          <el-button v-for="reason in rejectReasonOptions" :key="reason" size="small" @click="appendRejectReason(reason)">
            {{ reason }}
          </el-button>
        </div>
        <el-input v-model="rejectReason" type="textarea" :rows="5" placeholder="填写拒绝原因，号主会在通知中看到审核结果" />
      </div>
      <template #footer>
        <el-button @click="activeListing = null">取消</el-button>
        <el-button type="danger" :loading="submitting" @click="handleReject">确认拒绝</el-button>
      </template>
    </el-dialog>

    <el-dialog :model-value="!!priceAdjustListing" title="调整审核价格" width="560px" @update:model-value="priceAdjustListing = null">
      <div v-if="priceAdjustListing" class="dialog-body price-adjust-dialog">
        <div class="adjust-current">
          <div>
            <span>卖家发布价</span>
            <strong>{{ money(sellerTotalPrice(priceAdjustListing)) }}</strong>
          </div>
          <div>
            <span>调整后买家价</span>
            <strong class="preview-value">{{ previewMoney(priceAdjustPreview?.buyerTotalPrice || 0) }}</strong>
          </div>
          <div>
            <span>调整后买家比例</span>
            <strong class="preview-value">{{ ratioText(priceAdjustPreview?.buyerRatio || 0) }}</strong>
          </div>
        </div>
        <el-radio-group v-model="priceAdjustMode" class="adjust-mode">
          <el-radio-button label="ratio">按加价后比例</el-radio-button>
          <el-radio-button label="price">按加价后价格</el-radio-button>
        </el-radio-group>
        <label v-if="priceAdjustMode === 'ratio'" class="adjust-field">
          <span>加价后比例</span>
          <el-input-number v-model="priceAdjustForm.buyer_ratio" :min="0" :step="0.1" :controls="false" />
        </label>
        <label v-else class="adjust-field">
          <span>加价后价格</span>
          <el-input-number v-model="priceAdjustForm.buyer_total_price" :min="0" :step="1" :controls="false" />
        </label>
        <label class="adjust-field">
          <span>调价原因</span>
          <el-input v-model="priceAdjustForm.reason" type="textarea" :rows="3" placeholder="可填写调价原因，便于审计追踪" />
        </label>
      </div>
      <template #footer>
        <el-button @click="priceAdjustListing = null">取消</el-button>
        <el-button type="primary" :loading="adjustingPrice" @click="handleSavePriceAdjust">保存调整</el-button>
      </template>
    </el-dialog>

    <el-dialog :model-value="!!evidenceListing" title="账号资产截图" width="860px" @update:model-value="evidenceListing = null">
      <div v-if="evidenceListing" class="dialog-body">
        <p><strong>{{ evidenceListing.title }}</strong></p>
        <div v-if="evidenceListing.screenshot_urls?.length" class="screenshot-grid dialog-screenshots">
          <button v-for="url in evidenceListing.screenshot_urls" :key="url" type="button" class="screenshot-tile" @click="openScreenshot(url)">
            <img :src="previewURLs[url] || url" alt="账号截图" />
            <span v-if="isDefaultScreenshot(url)">默认图片</span>
          </button>
        </div>
        <p v-else>暂无截图</p>
      </div>
      <template #footer>
        <el-button @click="evidenceListing = null">关闭</el-button>
      </template>
    </el-dialog>
  </section>
</template>

<style scoped>
.review-page {
  min-height: calc(100vh - 128px);
}

.review-header {
  margin-bottom: 18px;
}

.review-summary {
  display: grid;
  grid-template-columns: repeat(4, minmax(120px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}

.summary-card {
  min-width: 0;
  border: 1px solid #e8edf4;
  border-radius: 8px;
  background: #ffffff;
  padding: 14px 16px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  text-align: left;
  cursor: pointer;
  transition: all 0.2s ease;
}

.summary-card:hover {
  border-color: #1477ff;
  box-shadow: 0 2px 8px rgba(20, 119, 255, 0.15);
  transform: translateY(-1px);
}

.summary-card.active {
  border-color: #1477ff;
  background: #1477ff;
  box-shadow: 0 2px 8px rgba(20, 119, 255, 0.25);
}

.summary-card.active span {
  color: #ffffff;
}

.summary-card.active strong {
  color: #ffffff;
}

.review-summary span,
.queue-footer span,
.panel-title span,
.inventory-grid span,
.resource-table span,
.price-decision-card span,
.adjust-current span,
.adjust-field span {
  color: #8f9bba;
  font-size: 12px;
  font-weight: 600;
}

.review-summary strong {
  display: block;
  margin-top: 5px;
  color: #1b2559;
  font-size: 24px;
  font-weight: 800;
}

.review-workbench {
  display: grid;
  grid-template-columns: minmax(360px, 420px) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.review-queue,
.review-detail,
.review-panel,
.review-hero-panel {
  min-width: 0;
}

.review-queue {
  position: sticky;
  top: 16px;
  border-radius: 8px;
  background: #ffffff;
  padding: 14px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}

.queue-toolbar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 120px;
  gap: 10px;
  margin-bottom: 12px;
}

.queue-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: calc(100vh - 300px);
  overflow: auto;
  padding-right: 2px;
}

.queue-item {
  width: 100%;
  min-height: 112px;
  border: 1px solid #e8ecf1;
  border-radius: 8px;
  background: #ffffff;
  padding: 12px;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
}

.queue-item:hover,
.queue-item.active {
  border-color: #4f7cff;
  background: #f8faff;
  box-shadow: 0 6px 18px rgba(79, 124, 255, 0.1);
}

.queue-title-row,
.queue-footer,
.panel-title,
.hero-title {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.queue-title-row strong {
  min-width: 0;
  color: #1b2559;
  font-size: 14px;
  font-weight: 800;
  line-height: 1.4;
}

.queue-title-row span {
  flex-shrink: 0;
  color: #4f7cff;
  font-weight: 800;
}

.queue-meta,
.risk-strip,
.skin-list,
.reject-reasons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.queue-meta {
  margin: 10px 0;
}

.queue-meta span {
  border-radius: 6px;
  background: #f0f2f5;
  padding: 4px 7px;
  color: #4b5563;
  font-size: 12px;
  font-weight: 700;
}

.queue-footer {
  align-items: center;
}

.review-detail {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.review-hero-panel,
.review-panel {
  border-radius: 8px;
  background: #ffffff;
  padding: 18px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}

.hero-title h2,
.panel-title h3 {
  margin: 0;
  color: #1b2559;
  font-size: 18px;
  font-weight: 800;
  line-height: 1.35;
}

.hero-title p {
  margin: 0 0 6px;
  color: #8f9bba;
  font-size: 12px;
  font-weight: 700;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

.risk-strip {
  margin-top: 14px;
}

.review-metrics {
  grid-template-columns: repeat(5, minmax(132px, 1fr));
  gap: 12px;
  margin-bottom: 0;
}

.review-metrics .metric-card {
  border-radius: 8px;
  padding: 16px;
}

.review-metrics .metric-card strong {
  font-size: 20px;
}

.review-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.panel-title {
  align-items: center;
  margin-bottom: 14px;
}

.price-panel-title p {
  margin: 5px 0 0;
  color: #8f9bba;
  font-size: 12px;
  font-weight: 600;
}

.price-decision-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(150px, 1fr));
  gap: 12px;
}

.price-decision-card {
  display: grid;
  gap: 7px;
  min-width: 0;
  min-height: 128px;
  border: 1px solid #e8ecf1;
  border-radius: 8px;
  background: #f8f9fe;
  padding: 14px;
}

.price-decision-card strong {
  color: #1b2559;
  font-size: 24px;
  font-weight: 900;
  line-height: 1.1;
}

.price-decision-card p,
.price-decision-card small {
  margin: 0;
  color: #4b5563;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.45;
}

.price-decision-card small {
  color: #8f9bba;
}

.price-decision-card.buyer {
  border-color: #bfdbfe;
  background: #eff6ff;
}

.price-decision-card.buyer strong {
  color: #2563eb;
}

.price-decision-card.rule {
  border-color: #fed7aa;
  background: #fff7ed;
}

.price-decision-card.rule strong {
  color: #ea580c;
}

.price-decision-card.deposit {
  border-color: #e0e7ff;
  background: #f5f7ff;
}

.detail-list {
  display: grid;
  gap: 10px;
  margin: 0;
}

.detail-list div {
  display: grid;
  grid-template-columns: 86px minmax(0, 1fr);
  gap: 10px;
  align-items: start;
}

.detail-list dt {
  color: #8f9bba;
  font-size: 12px;
  font-weight: 700;
}

.detail-list dd {
  min-width: 0;
  margin: 0;
  color: #1f2937;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.inventory-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(110px, 1fr));
  gap: 10px;
  margin-bottom: 12px;
}

.inventory-grid div {
  border-radius: 8px;
  background: #f8f9fe;
  padding: 12px;
}

.inventory-grid strong {
  display: block;
  margin-top: 4px;
  color: #1b2559;
  font-size: 22px;
  font-weight: 900;
}

.resource-table {
  display: grid;
  gap: 8px;
  margin-bottom: 12px;
}

.resource-table div {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) 72px minmax(120px, 1fr);
  gap: 10px;
  align-items: center;
  border-bottom: 1px solid #f0f2f5;
  padding-bottom: 8px;
}

.resource-table strong {
  color: #1f2937;
  font-size: 13px;
  font-weight: 800;
}

.resource-table em {
  color: #8f9bba;
  font-size: 12px;
  font-style: normal;
  font-weight: 600;
}

.skin-list {
  align-items: center;
}

.skin-list > span {
  color: #8f9bba;
  font-size: 13px;
}

.screenshot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
}

.screenshot-tile {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid #e8ecf1;
  border-radius: 8px;
  background: #f8f9fe;
  padding: 0;
  cursor: pointer;
}

.screenshot-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.screenshot-tile span {
  position: absolute;
  top: 8px;
  left: 8px;
  border-radius: 6px;
  background: rgba(245, 158, 11, 0.95);
  padding: 3px 7px;
  color: #ffffff;
  font-size: 12px;
  font-weight: 800;
}

.empty-warning {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 84px;
  border-radius: 8px;
  background: #fff7ed;
  padding: 16px;
  color: #c2410c;
  font-weight: 700;
}

.dialog-screenshots {
  max-height: 540px;
  overflow: auto;
}

.reject-dialog p {
  margin-top: 0;
}

.reject-reasons {
  margin: 12px 0;
}

.price-adjust-dialog {
  display: grid;
  gap: 14px;
}

.adjust-current {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

.adjust-current div {
  display: grid;
  gap: 5px;
  border-radius: 8px;
  background: #f8f9fe;
  padding: 12px;
}

.adjust-current strong {
  color: #1b2559;
  font-size: 18px;
  font-weight: 900;
}

.adjust-current .preview-value {
  color: #2563eb;
}

.adjust-mode {
  width: 100%;
}

.adjust-mode :deep(.el-radio-button) {
  width: 50%;
}

.adjust-mode :deep(.el-radio-button__inner) {
  width: 100%;
  border-color: #d7deea;
  font-weight: 800;
}

.adjust-mode :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
  border-color: #2563eb;
  background: #2563eb;
  box-shadow: -1px 0 0 0 #2563eb;
}

.adjust-field {
  display: grid;
  gap: 8px;
}

.adjust-field > span {
  color: #52607a;
  font-size: 12px;
  font-weight: 800;
}

.adjust-field :deep(.el-input-number) {
  width: 100%;
}

.empty-detail {
  min-height: 420px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  background: #ffffff;
}

@media (max-width: 1280px) {
  .review-workbench {
    grid-template-columns: 1fr;
  }

  .review-queue {
    position: static;
  }

  .queue-list {
    max-height: none;
  }
}

@media (max-width: 900px) {
  .review-summary,
  .price-decision-grid,
  .review-grid {
    grid-template-columns: 1fr;
  }

  .adjust-current {
    grid-template-columns: 1fr;
  }

  .hero-title {
    flex-direction: column;
  }

  .hero-actions {
    justify-content: flex-start;
  }

  .queue-toolbar {
    grid-template-columns: 1fr;
  }
}
</style>
