<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';

import type {
  CountryDashboardGameMetric,
  CountryDashboardMetric,
  CountryDashboardRechargeDetail,
  CountryDashboardResult,
} from '#/api/legacy/datav';

import {
  computed,
  nextTick,
  onBeforeUnmount,
  onMounted,
  reactive,
  ref,
  watch,
} from 'vue';

import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { useAccessStore } from '@vben/stores';

import {
  Button,
  DateRangePicker,
  Empty,
  Input,
  message,
  Modal,
  Select,
  Space,
  Spin,
  Table,
  TabPane,
  Tabs,
  Tooltip,
} from 'antdv-next';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';

import {
  countryDashboard,
  countryDashboardGameCountryRank,
  countryDashboardGameSummaries,
  countryDashboardRechargeDetails,
} from '#/api/legacy/datav';
import SysOriginSelect from '#/components/sys-origin-select.vue';
import { getAllowedSysOrigins } from '#/views/system/shared';

import LatestPurchaseList from './components/latest-purchase-list.vue';
import PurchaseCount from './components/purchase-count.vue';

defineOptions({ name: 'StatisticsDatav' });

dayjs.extend(utc);
dayjs.extend(timezone);

const DEFAULT_STAT_TIMEZONE = 'Asia/Riyadh';

const accessStore = useAccessStore();

const datavFullscreen = ref<HTMLElement>();
const fullscreen = ref(false);
const visualMode = ref(false);
const loading = ref(false);
const dashboard = ref<CountryDashboardResult | null>(null);
const activeTableTab = ref<'game' | 'overview' | 'recharge'>('overview');
const arpuDashboards = ref<{
  day: CountryDashboardResult | null;
  threeDay: CountryDashboardResult | null;
  week: CountryDashboardResult | null;
}>({
  day: null,
  threeDay: null,
  week: null,
});
const initialDate = dayjs().tz(DEFAULT_STAT_TIMEZONE);
const dateRange = ref<[string, string] | null>([
  initialDate.subtract(6, 'day').format('YYYY-MM-DD'),
  initialDate.format('YYYY-MM-DD'),
]);
const selectedCountryCodes = ref<string[]>([]);
const rechargeDetailOpen = ref(false);
const rechargeDetailLoading = ref(false);
const rechargeDetailRecords = ref<CountryDashboardRechargeDetail[]>([]);
const rechargeDetailPagination = reactive({
  current: 1,
  pageSize: 100,
  total: 0,
});
const gameMetricLoading = ref(false);
const gameMetricRecords = ref<CountryDashboardGameMetric[]>([]);
const gameMetricPagination = reactive({
  current: 1,
  pageSize: 50,
  total: 0,
});
const gameMetricSorter = reactive<{
  field?: string;
  order?: 'ascend' | 'descend';
}>({
  field: 'consumeAmount',
  order: 'descend',
});
const gameCountryRankOpen = ref(false);
const gameCountryRankLoading = ref(false);
const gameCountryRankRecords = ref<CountryDashboardGameMetric[]>([]);
const selectedGameMetric = ref<null | Partial<CountryDashboardGameMetric>>(null);
const gameQuery = reactive<{
  gameKeyword: string;
  gameProvider?: string;
}>({
  gameKeyword: '',
  gameProvider: undefined,
});

const query = reactive({
  periodType: 'DAY',
  statTimezone: DEFAULT_STAT_TIMEZONE,
  sysOrigin: '',
});

const userRechargeChartRef = ref<EchartsUIType>();
const flowChartRef = ref<EchartsUIType>();
const profitChartRef = ref<EchartsUIType>();
const luckyChartRef = ref<EchartsUIType>();
const { renderEcharts: renderUserRechargeChart } = useEcharts(userRechargeChartRef);
const { renderEcharts: renderFlowChart } = useEcharts(flowChartRef);
const { renderEcharts: renderProfitChart } = useEcharts(profitChartRef);
const { renderEcharts: renderLuckyChart } = useEcharts(luckyChartRef);
type ChartOption = Parameters<typeof renderUserRechargeChart>[0];

const accessCodes = computed(() => accessStore.accessCodes || []);
const gameCountryRankTitle = computed(() => {
  const game = selectedGameMetric.value;
  if (!game) {
    return '国家消耗排名';
  }
  return `${gameDisplayText(game)} 国家消耗排名`;
});
const sysOriginOptions = computed(() => {
  const options = getAllowedSysOrigins(accessCodes.value);
  return options.length > 0 ? options : getAllowedSysOrigins([]);
});
const permissionsSysOriginCode = computed(() =>
  sysOriginOptions.value.map((item) => String(item.value || '')),
);

const periodTypeOptions = [
  { label: '日', value: 'DAY' },
  { label: '周', value: 'WEEK' },
  { label: '月', value: 'MONTH' },
  { label: '全部', value: 'ALL' },
];

const statTimezoneOptions = [
  { label: '利雅得（服务器）', value: 'Asia/Riyadh' },
  { label: 'UTC', value: 'UTC' },
  { label: '北京', value: 'Asia/Shanghai' },
];

const gameProviderOptions = [
  { label: '全部游戏厂商', value: '' },
  { label: '站内游戏', value: 'INTERNAL' },
  { label: '百顺', value: 'BAISHUN' },
  { label: 'Game Open', value: 'GAME_OPEN' },
  { label: '灵犀', value: 'LINGXIAN' },
  { label: 'Yomi', value: 'YOMI' },
  { label: 'HKYS', value: 'HKYS' },
  { label: 'Hot', value: 'HOT' },
  { label: '未识别', value: 'UNKNOWN' },
];

const metricColumns = new Set([
  'countryNewUser',
  'd1RetentionRate',
  'd7RetentionRate',
  'd30RetentionRate',
  'dailyActiveUser',
  'dealerRecharge',
  'gamePayout',
  'gamePayoutRate',
  'gameProfit',
  'gameProfitRate',
  'gameTotalFlow',
  'gameUser',
  'giftConsume',
  'googleRecharge',
  'luckyGiftPayout',
  'luckyGiftPayoutRate',
  'luckyGiftProfit',
  'luckyGiftProfitRate',
  'luckyGiftTotalFlow',
  'luckyGiftUser',
  'luckyGiftUserRate',
  'mifapayRecharge',
  'newUserRecharge',
  'salaryExchange',
  'totalRecharge',
]);

const integerMetricColumns = new Set([
  'countryNewUser',
  'dailyActiveUser',
  'gameUser',
  'luckyGiftUser',
]);

const percentMetricColumns = new Set([
  'd1RetentionRate',
  'd7RetentionRate',
  'd30RetentionRate',
  'gamePayoutRate',
  'gameProfitRate',
  'luckyGiftPayoutRate',
  'luckyGiftProfitRate',
  'luckyGiftUserRate',
]);

const summaryMetricKeys = [
  'countryNewUser',
  'dailyActiveUser',
  'newUserRecharge',
  'dealerRecharge',
  'mifapayRecharge',
  'googleRecharge',
  'salaryExchange',
  'totalRecharge',
  'giftConsume',
  'luckyGiftTotalFlow',
  'luckyGiftUser',
  'luckyGiftPayout',
  'luckyGiftProfit',
  'gameTotalFlow',
  'gameUser',
  'gamePayout',
  'gameProfit',
  'd1RetentionUser',
  'd1RetentionBaseUser',
  'd7RetentionUser',
  'd7RetentionBaseUser',
  'd30RetentionUser',
  'd30RetentionBaseUser',
] as const;

const summaryMetricDefinitions = [
  { key: 'countryNewUser', label: '新增' },
  { key: 'dailyActiveUser', label: '当日活跃' },
  { key: 'newUserRecharge', label: '新增充值' },
  { key: 'dealerRecharge', label: '币商充值' },
  { key: 'mifapayRecharge', label: 'MifaPay 充值' },
  { key: 'googleRecharge', label: 'Google 充值' },
  { key: 'salaryExchange', label: '工资兑换' },
  { key: 'totalRecharge', label: '总充值' },
  { key: 'giftConsume', label: '礼物消耗' },
  { key: 'luckyGiftTotalFlow', label: '幸运礼物流水' },
  { key: 'luckyGiftUser', label: '幸运礼物参与人数' },
  { key: 'luckyGiftUserRate', label: '幸运礼物付费率' },
  { key: 'luckyGiftPayout', label: '幸运礼物返奖' },
  { key: 'luckyGiftPayoutRate', label: '幸运礼物返奖率' },
  { key: 'luckyGiftProfit', label: '幸运礼物利润' },
  { key: 'luckyGiftProfitRate', label: '幸运礼物利润率' },
  { key: 'gameTotalFlow', label: '游戏流水' },
  { key: 'gameUser', label: '游戏参与人数' },
  { key: 'gamePayout', label: '游戏返奖' },
  { key: 'gamePayoutRate', label: '游戏返奖率' },
  { key: 'gameProfit', label: '游戏利润' },
  { key: 'gameProfitRate', label: '游戏利润率' },
  { key: 'd1RetentionRate', label: '次日留存率' },
  { key: 'd7RetentionRate', label: '七日留存率' },
  { key: 'd30RetentionRate', label: '30 日留存率' },
] as const;

type SummaryMetricKey = (typeof summaryMetricDefinitions)[number]['key'];
type SummaryMetric = Record<string, number>;

const summaryColumns = [
  ...summaryMetricDefinitions.map((item) => ({
    align: 'right' as const,
    dataIndex: item.key,
    key: item.key,
    title: item.label,
    width: item.key === 'countryNewUser' ? 110 : (String(item.key).includes('Rate') ? 160 : 150),
  })),
];
const summaryScrollX = summaryColumns.reduce((totalWidth, item) => totalWidth + item.width, 0);

const overviewColumns: any[] = [
  { dataIndex: 'countryName', fixed: 'left' as const, key: 'countryName', title: '国家', width: 150 },
  { dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 86 },
  { dataIndex: 'periodName', key: 'periodName', title: '周期', width: 110 },
  { align: 'right', dataIndex: 'countryNewUser', key: 'countryNewUser', title: '新增', width: 110 },
  { align: 'right', dataIndex: 'dailyActiveUser', key: 'dailyActiveUser', title: '当日活跃', width: 120 },
  { align: 'right', dataIndex: 'd1RetentionRate', key: 'd1RetentionRate', title: '次日留存率', width: 140 },
  { align: 'right', dataIndex: 'd7RetentionRate', key: 'd7RetentionRate', title: '七日留存率', width: 140 },
  { align: 'right', dataIndex: 'd30RetentionRate', key: 'd30RetentionRate', title: '30 日留存率', width: 150 },
  { align: 'right', dataIndex: 'newUserRecharge', key: 'newUserRecharge', title: '新增充值', width: 130 },
  { align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商充值', width: 130 },
  { align: 'right', dataIndex: 'mifapayRecharge', key: 'mifapayRecharge', title: 'MifaPay 充值', width: 150 },
  { align: 'right', dataIndex: 'googleRecharge', key: 'googleRecharge', title: 'Google 充值', width: 140 },
  { align: 'right', dataIndex: 'salaryExchange', key: 'salaryExchange', title: '工资兑换', width: 130 },
  { align: 'right', dataIndex: 'totalRecharge', key: 'totalRecharge', title: '总充值', width: 130 },
  { align: 'right', dataIndex: 'giftConsume', key: 'giftConsume', title: '礼物消耗', width: 130 },
  { align: 'right', dataIndex: 'luckyGiftTotalFlow', key: 'luckyGiftTotalFlow', title: '幸运礼物流水', width: 150 },
  { align: 'right', dataIndex: 'luckyGiftUser', key: 'luckyGiftUser', title: '幸运礼物参与人数', width: 170 },
  { align: 'right', dataIndex: 'luckyGiftUserRate', key: 'luckyGiftUserRate', title: '幸运礼物付费率', width: 160 },
  { align: 'right', dataIndex: 'luckyGiftPayout', key: 'luckyGiftPayout', title: '幸运礼物返奖', width: 150 },
  { align: 'right', dataIndex: 'luckyGiftPayoutRate', key: 'luckyGiftPayoutRate', title: '幸运礼物返奖率', width: 160 },
  { align: 'right', dataIndex: 'luckyGiftProfit', key: 'luckyGiftProfit', title: '幸运礼物利润', width: 150 },
  { align: 'right', dataIndex: 'luckyGiftProfitRate', key: 'luckyGiftProfitRate', title: '幸运礼物利润率', width: 160 },
  { align: 'right', dataIndex: 'gameTotalFlow', key: 'gameTotalFlow', title: '游戏流水', width: 130 },
  { align: 'right', dataIndex: 'gameUser', key: 'gameUser', title: '游戏参与人数', width: 140 },
  { align: 'right', dataIndex: 'gamePayout', key: 'gamePayout', title: '游戏返奖', width: 130 },
  { align: 'right', dataIndex: 'gamePayoutRate', key: 'gamePayoutRate', title: '游戏返奖率', width: 140 },
  { align: 'right', dataIndex: 'gameProfit', key: 'gameProfit', title: '游戏利润', width: 130 },
  { align: 'right', dataIndex: 'gameProfitRate', key: 'gameProfitRate', title: '游戏利润率', width: 140 },
].map((column) => ({
  ...column,
  sortDirections: ['ascend', 'descend'],
  sorter: compareTableColumn(String(column.dataIndex)),
}));

const rechargeColumns: any[] = [
  { dataIndex: 'countryName', fixed: 'left' as const, key: 'countryName', title: '国家', width: 150 },
  { dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 86 },
  { dataIndex: 'periodName', key: 'periodName', title: '周期', width: 110 },
  { align: 'right', dataIndex: 'dealerRecharge', key: 'dealerRecharge', title: '币商充值', width: 140 },
  { align: 'right', dataIndex: 'mifapayRecharge', key: 'mifapayRecharge', title: 'MifaPay 充值', width: 160 },
  { align: 'right', dataIndex: 'googleRecharge', key: 'googleRecharge', title: 'Google 充值', width: 150 },
  { align: 'right', dataIndex: 'totalRecharge', key: 'totalRecharge', title: '总充值', width: 140 },
].map((column) => ({
  ...column,
  sortDirections: ['ascend', 'descend'],
  sorter: compareTableColumn(String(column.dataIndex)),
}));

const gameMetricNumberColumns = new Set([
  'consumeAmount',
  'orderCount',
  'payoutAmount',
  'profitAmount',
  'userCount',
]);

const gameMetricIntegerColumns = new Set(['orderCount', 'userCount']);

const gameMetricPercentColumns = new Set(['payoutRate', 'profitRate']);

const gameMetricColumns: any[] = [
  { align: 'right' as const, dataIndex: 'rank', fixed: 'left' as const, key: 'rank', title: '排名', width: 72 },
  { dataIndex: 'gameName', fixed: 'left' as const, key: 'gameName', title: '游戏', width: 240 },
  { dataIndex: 'gameProvider', key: 'gameProvider', title: '游戏厂商', width: 130 },
  { dataIndex: 'gameId', key: 'gameId', title: '游戏 ID', width: 150 },
  {
    align: 'right' as const,
    dataIndex: 'consumeAmount',
    key: 'consumeAmount',
    sortDirections: ['descend', 'ascend'],
    sorter: true,
    title: '消耗',
    width: 130,
  },
  { dataIndex: 'refreshedAt', key: 'refreshedAt', title: '更新时间', width: 170 },
];
const gameTableScrollX = gameMetricColumns.reduce(
  (totalWidth, item) => totalWidth + Number(item.width || 0),
  0,
);

const gameCountryRankColumns: any[] = [
  { align: 'right' as const, dataIndex: 'rank', key: 'rank', title: '排名', width: 72 },
  { dataIndex: 'countryName', key: 'countryName', title: '国家', width: 180 },
  { dataIndex: 'countryCode', key: 'countryCode', title: 'Code', width: 100 },
  { align: 'right' as const, dataIndex: 'consumeAmount', key: 'consumeAmount', title: '消耗', width: 150 },
  { dataIndex: 'refreshedAt', key: 'refreshedAt', title: '更新时间', width: 170 },
];
const gameCountryRankScrollX = gameCountryRankColumns.reduce(
  (totalWidth, item) => totalWidth + Number(item.width || 0),
  0,
);

const rechargeDetailColumns: any[] = [
  { dataIndex: 'createTime', key: 'createTime', title: '时间', width: 170 },
  { dataIndex: 'sourceType', key: 'sourceType', title: '来源', width: 120 },
  { dataIndex: 'user', key: 'user', title: '用户', width: 240 },
  { dataIndex: 'country', key: 'country', title: '国家', width: 160 },
  { align: 'right', dataIndex: 'amount', key: 'amount', title: '充值金额', width: 120 },
  { align: 'right', dataIndex: 'coinQuantity', key: 'coinQuantity', title: '金币', width: 130 },
  { dataIndex: 'sourceChannel', key: 'sourceChannel', title: '渠道/类型', width: 150 },
  { dataIndex: 'remark', key: 'remark', title: '备注', width: 220 },
];
const rechargeDetailScrollX = rechargeDetailColumns.reduce(
  (totalWidth, item) => totalWidth + Number(item.width || 0),
  0,
);
const rechargeSourceNameMap: Record<string, string> = {
  DEALER: '币商充值',
  MIFAPAY: 'MifaPay 充值',
  OFFICIAL: '官方充值',
};

const activeColumns = computed(() =>
  activeTableTab.value === 'recharge' ? rechargeColumns : overviewColumns,
);
const tableScrollX = computed(() =>
  activeColumns.value.reduce((totalWidth, item) => totalWidth + Number(item.width || 0), 0),
);

const rawRecords = computed(() => dashboard.value?.records || []);
const countryOptions = computed(() => {
  const optionMap = new Map<string, string>();
  for (const item of rawRecords.value) {
    const code = String(item.countryCode || '');
    if (!code || code === 'ALL') {
      continue;
    }
    optionMap.set(code, `${item.countryName || code} / ${code}`);
  }
  return [...optionMap.entries()]
    .map(([value, label]) => ({ label, value }))
    .toSorted((current, next) => current.label.localeCompare(next.label));
});
const records = computed(() => {
  return filterRecordsBySelectedCountries(rawRecords.value);
});
const chartRecords = computed(() => records.value.filter((item) => item.countryCode !== 'ALL'));
const visibleSummary = computed(() => buildVisibleSummary(records.value));
const summaryDataSource = computed(() => {
  const row: Record<string, string> = { key: 'summary' };
  for (const item of summaryMetricDefinitions) {
    row[item.key] = formatSummaryValue(item.key, visibleSummary.value);
  }
  return [row];
});
const luckyGameCards = computed(() => {
  const summary = visibleSummary.value;
  return [
    { label: '幸运礼物参与人数', value: formatInteger(summary.luckyGiftUser) },
    { label: '幸运礼物付费率', value: formatPercent(summary.luckyGiftUserRate) },
    { label: '幸运礼物返奖率', value: formatPercent(summary.luckyGiftPayoutRate) },
    { label: '游戏参与人数', value: formatInteger(summary.gameUser) },
    { label: '游戏返奖率', value: formatPercent(summary.gamePayoutRate) },
    { label: '游戏利润率', value: formatPercent(summary.gameProfitRate) },
  ];
});
const appDataCards = computed(() => {
  const summary = visibleSummary.value;
  return [
    { label: '当日活跃数', value: formatInteger(summary.dailyActiveUser) },
    { label: '次日留存率', value: formatPercent(summary.d1RetentionRate) },
    { label: '七日留存率', value: formatPercent(summary.d7RetentionRate) },
    { label: '30 日留存率', value: formatPercent(summary.d30RetentionRate) },
  ];
});
const arpuCards = computed(() => {
  return [
    { label: '日 ARPU', value: formatArpu(arpuDashboards.value.day) },
    { label: '3 日 ARPU', value: formatArpu(arpuDashboards.value.threeDay) },
    { label: '周 ARPU', value: formatArpu(arpuDashboards.value.week) },
    { label: '筛选 ARPU', value: formatAmount(calcArpu(visibleSummary.value)) },
  ];
});
const totalRechargeCard = computed(() => ({
  label: '总充值',
  value: formatAmount(visibleSummary.value.totalRecharge),
}));

function hasPermission(code: string) {
  const codes = accessCodes.value;
  return codes.length === 0 || codes.includes(code);
}

const canQuery = computed(() => hasPermission('datav:query'));
const canPaidPreview = computed(() => hasPermission('datav:paid:preview'));
const currentTimezoneLabel = computed(() => {
  return statTimezoneOptions.find((item) => item.value === query.statTimezone)?.label || query.statTimezone;
});

function syncFullscreenState() {
  fullscreen.value = document.fullscreenElement === datavFullscreen.value;
}

async function screen() {
  const element = datavFullscreen.value;
  if (!element) {
    return;
  }
  if (document.fullscreenElement === element) {
    await document.exitFullscreen();
    return;
  }
  if (document.fullscreenElement) {
    await document.exitFullscreen();
  }
  await element.requestFullscreen();
}

async function toggleVisualMode() {
  visualMode.value = !visualMode.value;
  await renderVisualCharts();
}

function defaultRange(periodType: string): [string, string] | null {
  const now = dayjs().tz(query.statTimezone || DEFAULT_STAT_TIMEZONE);
  if (periodType === 'ALL') {
    return null;
  }
  if (periodType === 'MONTH') {
    return [now.startOf('month').format('YYYY-MM-DD'), now.format('YYYY-MM-DD')];
  }
  if (periodType === 'WEEK') {
    const daysSinceMonday = now.day() === 0 ? 6 : now.day() - 1;
    return [now.subtract(daysSinceMonday, 'day').format('YYYY-MM-DD'), now.format('YYYY-MM-DD')];
  }
  return [now.subtract(6, 'day').format('YYYY-MM-DD'), now.format('YYYY-MM-DD')];
}

function handlePeriodChange(value: string) {
  dateRange.value = defaultRange(value);
  loadDashboard();
}

function handleTimezoneChange() {
  dateRange.value = defaultRange(query.periodType);
  loadDashboard();
}

function buildParams() {
  const params: Record<string, any> = {
    periodType: query.periodType,
    statTimezone: query.statTimezone || DEFAULT_STAT_TIMEZONE,
    sysOrigin: query.sysOrigin || undefined,
  };
  if (dateRange.value?.[0] && dateRange.value?.[1]) {
    params.startDate = dateRange.value[0];
    params.endDate = dateRange.value[1];
  }
  return params;
}

function buildRecentDayParams(dayCount: number) {
  const end = dayjs().tz(query.statTimezone || DEFAULT_STAT_TIMEZONE);
  const start = end.subtract(dayCount - 1, 'day');
  return {
    periodType: 'DAY',
    startDate: start.format('YYYY-MM-DD'),
    endDate: end.format('YYYY-MM-DD'),
    statTimezone: query.statTimezone || DEFAULT_STAT_TIMEZONE,
    sysOrigin: query.sysOrigin || undefined,
  };
}

async function loadDashboard() {
  if (!canQuery.value) {
    return;
  }
  loading.value = true;
  try {
    const [mainDashboard, dayArpuDashboard, threeDayArpuDashboard, weekArpuDashboard] =
      await Promise.all([
        countryDashboard(buildParams()),
        countryDashboard(buildRecentDayParams(1)),
        countryDashboard(buildRecentDayParams(3)),
        countryDashboard(buildRecentDayParams(7)),
      ]);
    dashboard.value = mainDashboard;
    arpuDashboards.value = {
      day: dayArpuDashboard,
      threeDay: threeDayArpuDashboard,
      week: weekArpuDashboard,
    };
    if (activeTableTab.value === 'game') {
      await loadGameMetrics();
      if (gameCountryRankOpen.value) {
        await loadGameCountryRank();
      }
    }
  } catch (error) {
    dashboard.value = null;
    arpuDashboards.value = {
      day: null,
      threeDay: null,
      week: null,
    };
    message.error('数据大屏加载失败');
    throw error;
  } finally {
    loading.value = false;
  }
  renderVisualCharts();
}

function buildGameMetricParams() {
  return {
    ...buildParams(),
    countryCodes: selectedCountryCodes.value.join(',') || undefined,
    cursor: gameMetricPagination.current,
    gameKeyword: gameQuery.gameKeyword.trim() || undefined,
    gameProvider: gameQuery.gameProvider || undefined,
    limit: gameMetricPagination.pageSize,
    sortField: gameMetricSorter.field || undefined,
    sortOrder: gameMetricSorter.order || undefined,
  };
}

async function loadGameMetrics() {
  if (!canQuery.value) {
    return;
  }
  gameMetricLoading.value = true;
  try {
    const result = await countryDashboardGameSummaries(buildGameMetricParams());
    gameMetricRecords.value = result.records || [];
    gameMetricPagination.total = Number(result.total || 0);
    gameMetricPagination.current = Number(result.current || gameMetricPagination.current || 1);
    const resultPageSize = Number(result.size);
    gameMetricPagination.pageSize =
      Number.isFinite(resultPageSize) && resultPageSize > 0
        ? resultPageSize
        : gameMetricPagination.pageSize;
  } catch {
    gameMetricRecords.value = [];
    gameMetricPagination.total = 0;
    message.error('游戏数据加载失败');
  } finally {
    gameMetricLoading.value = false;
  }
}

function buildGameCountryRankParams(record: Partial<CountryDashboardGameMetric>) {
  return {
    ...buildParams(),
    countryCodes: selectedCountryCodes.value.join(',') || undefined,
    gameId: record.gameId,
    gameProvider: record.gameProvider,
    rankLimit: 200,
  };
}

async function loadGameCountryRank(record = selectedGameMetric.value) {
  if (!record?.gameId || !record.gameProvider || !canQuery.value) {
    return;
  }
  gameCountryRankLoading.value = true;
  try {
    gameCountryRankRecords.value = await countryDashboardGameCountryRank(
      buildGameCountryRankParams(record),
    );
  } catch {
    gameCountryRankRecords.value = [];
    message.error('游戏国家排名加载失败');
  } finally {
    gameCountryRankLoading.value = false;
  }
}

async function openGameCountryRank(record: Partial<CountryDashboardGameMetric>) {
  selectedGameMetric.value = record;
  gameCountryRankOpen.value = true;
  await loadGameCountryRank(record);
}

async function handleGameMetricTableChange(
  pagination: Record<string, any>,
  _filters: Record<string, any>,
  sorter: Record<string, any> | Record<string, any>[],
) {
  const activeSorter = Array.isArray(sorter) ? sorter[0] : sorter;
  gameMetricPagination.current = Number(pagination.current || 1);
  gameMetricPagination.pageSize = Number(pagination.pageSize || gameMetricPagination.pageSize || 50);
  gameMetricSorter.field = activeSorter?.field ? String(activeSorter.field) : undefined;
  gameMetricSorter.order =
    activeSorter?.order === 'ascend' || activeSorter?.order === 'descend'
      ? activeSorter.order
      : undefined;
  await loadGameMetrics();
}

async function handleGameMetricFilterChange() {
  gameMetricPagination.current = 1;
  if (activeTableTab.value === 'game') {
    await loadGameMetrics();
  }
}

async function loadRechargeDetails() {
  rechargeDetailLoading.value = true;
  try {
    const result = await countryDashboardRechargeDetails({
      ...buildParams(),
      countryCodes: selectedCountryCodes.value.join(',') || undefined,
      cursor: rechargeDetailPagination.current,
      limit: rechargeDetailPagination.pageSize,
    });
    rechargeDetailRecords.value = result.records || [];
    rechargeDetailPagination.total = Number(result.total || 0);
  } finally {
    rechargeDetailLoading.value = false;
  }
}

async function openRechargeDetails() {
  rechargeDetailOpen.value = true;
  rechargeDetailPagination.current = 1;
  await loadRechargeDetails();
}

async function handleRechargeDetailTableChange(pagination: Record<string, any>) {
  rechargeDetailPagination.current = Number(pagination.current || 1);
  await loadRechargeDetails();
}

function rechargeDetailRowKey(record: Record<string, any>) {
  return `${record.sourceType}-${record.recordId}`;
}

function gameMetricRowKey(record: Record<string, any>) {
  return [record.gameProvider, record.gameId].join('-');
}

function gameCountryRankRowKey(record: Record<string, any>) {
  return [record.countryCode, record.gameProvider, record.gameId].join('-');
}

function gameMetricRank(index: number) {
  return (gameMetricPagination.current - 1) * gameMetricPagination.pageSize + index + 1;
}

function gameDisplayText(record: Partial<CountryDashboardGameMetric>) {
  return record.gameName || record.gameId || '-';
}

function rechargeSourceName(sourceType: string) {
  return rechargeSourceNameMap[sourceType] || sourceType || '-';
}

function rechargeDetailUserText(record: Record<string, any>) {
  const account = record.userAccount || record.userId;
  return [account, record.userNickname].filter(Boolean).join(' / ') || '-';
}

function rechargeDetailCountryText(record: Record<string, any>) {
  return [record.countryName, record.countryCode].filter(Boolean).join(' / ') || '-';
}

function showRechargeDetailTotal(total: number) {
  return `共 ${total} 条`;
}

function showGameMetricTotal(total: number) {
  return `共 ${total} 条`;
}

function toAmount(value?: null | number | string) {
  const number = Number(value || 0);
  return Number.isFinite(number) ? number : 0;
}

function calcPercent(numerator?: null | number | string, denominator?: null | number | string) {
  const fixedDenominator = toAmount(denominator);
  if (fixedDenominator === 0) {
    return 0;
  }
  return (toAmount(numerator) / fixedDenominator) * 100;
}

function formatInteger(value?: null | number | string) {
  const number = Number(value || 0);
  return Number.isFinite(number) ? number.toLocaleString('en-US', { maximumFractionDigits: 0 }) : '0';
}

function formatAmount(value?: null | number | string) {
  const number = Number(value || 0);
  return Number.isFinite(number)
    ? number.toLocaleString('en-US', {
        maximumFractionDigits: 2,
        minimumFractionDigits: 0,
      })
    : '0';
}

function formatPercent(value?: null | number | string) {
  const number = Number(value || 0);
  return Number.isFinite(number)
    ? `${number.toLocaleString('en-US', {
        maximumFractionDigits: 2,
        minimumFractionDigits: 2,
      })}%`
    : '0.00%';
}

function resolveLuckyGiftProfitRate(record?: null | {
  luckyGiftProfit?: number | string;
  luckyGiftProfitRate?: number | string;
  luckyGiftTotalFlow?: number | string;
}) {
  if (!record) {
    return 0;
  }
  if (
    record.luckyGiftProfitRate !== undefined &&
    record.luckyGiftProfitRate !== null &&
    record.luckyGiftProfitRate !== ''
  ) {
    const rate = Number(record.luckyGiftProfitRate);
    if (Number.isFinite(rate)) {
      return rate;
    }
  }
  const flow = Number(record.luckyGiftTotalFlow || 0);
  if (!Number.isFinite(flow) || flow === 0) {
    return 0;
  }
  return (Number(record.luckyGiftProfit || 0) / flow) * 100;
}

function resolveMetricRate(record: Record<string, any>, key: string) {
  if (key === 'luckyGiftProfitRate') {
    return resolveLuckyGiftProfitRate(record);
  }
  const rate = Number(record[key]);
  if (Number.isFinite(rate) && record[key] !== undefined && record[key] !== null && record[key] !== '') {
    return rate;
  }
  if (key === 'luckyGiftUserRate') {
    return calcPercent(record.luckyGiftUser, record.dailyActiveUser);
  }
  if (key === 'luckyGiftPayoutRate') {
    return calcPercent(record.luckyGiftPayout, record.luckyGiftTotalFlow);
  }
  if (key === 'gamePayoutRate') {
    return calcPercent(record.gamePayout, record.gameTotalFlow);
  }
  if (key === 'gameProfitRate') {
    return calcPercent(record.gameProfit, record.gameTotalFlow);
  }
  if (key === 'd1RetentionRate') {
    return calcPercent(record.d1RetentionUser, record.d1RetentionBaseUser);
  }
  if (key === 'd7RetentionRate') {
    return calcPercent(record.d7RetentionUser, record.d7RetentionBaseUser);
  }
  if (key === 'd30RetentionRate') {
    return calcPercent(record.d30RetentionUser, record.d30RetentionBaseUser);
  }
  return 0;
}

function calcArpu(summary: Record<string, any>) {
  const activeUser = toAmount(summary.dailyActiveUser);
  if (activeUser === 0) {
    return 0;
  }
  return toAmount(summary.totalRecharge) / activeUser;
}

function formatArpu(result?: CountryDashboardResult | null) {
  const summary = buildVisibleSummary(filterRecordsBySelectedCountries(result?.records || []));
  return formatAmount(calcArpu(summary));
}

function emptySummary(): SummaryMetric {
  return {
    countryNewUser: 0,
    d1RetentionBaseUser: 0,
    d1RetentionRate: 0,
    d1RetentionUser: 0,
    d7RetentionBaseUser: 0,
    d7RetentionRate: 0,
    d7RetentionUser: 0,
    d30RetentionBaseUser: 0,
    d30RetentionRate: 0,
    d30RetentionUser: 0,
    dailyActiveUser: 0,
    dealerRecharge: 0,
    googleRecharge: 0,
    gamePayout: 0,
    gamePayoutRate: 0,
    gameProfit: 0,
    gameProfitRate: 0,
    gameTotalFlow: 0,
    gameUser: 0,
    giftConsume: 0,
    luckyGiftPayout: 0,
    luckyGiftPayoutRate: 0,
    luckyGiftProfit: 0,
    luckyGiftProfitRate: 0,
    luckyGiftTotalFlow: 0,
    luckyGiftUser: 0,
    luckyGiftUserRate: 0,
    mifapayRecharge: 0,
    newUserRecharge: 0,
    salaryExchange: 0,
    totalRecharge: 0,
  };
}

function buildVisibleSummary(list: CountryDashboardMetric[]) {
  const summary = emptySummary();
  for (const item of list) {
    for (const key of summaryMetricKeys) {
      summary[key] = toAmount(summary[key]) + toAmount(item[key] as number | string);
    }
  }
  summary.luckyGiftUserRate = calcPercent(summary.luckyGiftUser, summary.dailyActiveUser);
  summary.luckyGiftPayoutRate = calcPercent(summary.luckyGiftPayout, summary.luckyGiftTotalFlow);
  summary.luckyGiftProfitRate = resolveLuckyGiftProfitRate(summary);
  summary.gamePayoutRate = calcPercent(summary.gamePayout, summary.gameTotalFlow);
  summary.gameProfitRate = calcPercent(summary.gameProfit, summary.gameTotalFlow);
  summary.d1RetentionRate = calcPercent(summary.d1RetentionUser, summary.d1RetentionBaseUser);
  summary.d7RetentionRate = calcPercent(summary.d7RetentionUser, summary.d7RetentionBaseUser);
  summary.d30RetentionRate = calcPercent(summary.d30RetentionUser, summary.d30RetentionBaseUser);
  return summary;
}

function formatSummaryValue(key: SummaryMetricKey, summary: SummaryMetric) {
  if (integerMetricColumns.has(key)) {
    return formatInteger(summary[key]);
  }
  if (percentMetricColumns.has(key)) {
    return formatPercent(resolveMetricRate(summary, key));
  }
  return formatAmount(summary[key]);
}

function filterCountry(input: string, option?: Record<string, any>) {
  const keyword = input.toLowerCase();
  const label = String(option?.label || '').toLowerCase();
  const value = String(option?.value || '').toLowerCase();
  return label.includes(keyword) || value.includes(keyword);
}

function tableCellText(record: Record<string, any>, key: string) {
  if (integerMetricColumns.has(key)) {
    return formatInteger(record[key] as number | string);
  }
  if (percentMetricColumns.has(key)) {
    return formatPercent(resolveMetricRate(record, key));
  }
  return formatAmount(record[key] as number | string);
}

function gameMetricCellText(record: Record<string, any>, key: string) {
  if (gameMetricIntegerColumns.has(key)) {
    return formatInteger(record[key] as number | string);
  }
  if (gameMetricPercentColumns.has(key)) {
    return formatPercent(record[key] as number | string);
  }
  if (gameMetricNumberColumns.has(key)) {
    return formatAmount(record[key] as number | string);
  }
  return record[key] || '-';
}

function compareTableColumn(key: string) {
  return (current: Record<string, any>, next: Record<string, any>) => {
    if (metricColumns.has(key)) {
      const currentValue = percentMetricColumns.has(key)
        ? resolveMetricRate(current, key)
        : toAmount(current[key]);
      const nextValue = percentMetricColumns.has(key)
        ? resolveMetricRate(next, key)
        : toAmount(next[key]);
      return currentValue - nextValue;
    }
    return String(current[key] || '').localeCompare(String(next[key] || ''));
  };
}

function filterRecordsBySelectedCountries(list: CountryDashboardMetric[]) {
  const selected = new Set(selectedCountryCodes.value);
  if (selected.size === 0) {
    return list;
  }
  return list.filter((item) => selected.has(String(item.countryCode || '')));
}

function rowKey(record: Record<string, any>) {
  return `${record.countryCode}-${record.periodKey}`;
}

function chartCategory(record: CountryDashboardMetric) {
  return `${record.periodName || '-'}\n${record.countryName || record.countryCode || '-'}`;
}

function amountSeries(name: string, dataIndex: keyof CountryDashboardMetric, list: CountryDashboardMetric[]) {
  return {
    data: list.map((item) => toAmount(item[dataIndex] as number | string)),
    emphasis: { focus: 'series' },
    name,
    type: 'bar',
  };
}

function lineSeries(name: string, dataIndex: keyof CountryDashboardMetric, list: CountryDashboardMetric[]) {
  return {
    data: list.map((item) => toAmount(item[dataIndex] as number | string)),
    emphasis: { focus: 'series' },
    name,
    smooth: true,
    type: 'line',
  };
}

function valueYAxis(labelFormatter?: string) {
  return {
    axisLabel: {
      color: '#94a3b8',
      formatter: labelFormatter,
    },
    splitLine: { lineStyle: { color: 'rgba(148, 163, 184, 0.16)' } },
    type: 'value' as const,
  };
}

function baseChartOption(title: string, list: CountryDashboardMetric[], series: any[]): ChartOption {
  return {
    backgroundColor: 'transparent',
    dataZoom: [
      { bottom: 6, height: 18, type: 'slider' },
      { type: 'inside' },
    ],
    grid: {
      bottom: 58,
      containLabel: true,
      left: 16,
      right: 20,
      top: 74,
    },
    legend: {
      textStyle: { color: '#cbd5e1' },
      top: 30,
      type: 'scroll',
    },
    series,
    textStyle: {
      color: '#e2e8f0',
    },
    title: {
      left: 16,
      text: title,
      textStyle: {
        color: '#f8fafc',
        fontSize: 15,
        fontWeight: 700,
      },
    },
    tooltip: {
      trigger: 'axis',
    },
    xAxis: {
      axisLabel: {
        color: '#94a3b8',
        interval: 0,
      },
      axisLine: { lineStyle: { color: '#334155' } },
      data: list.map((item) => chartCategory(item)),
      type: 'category',
    },
    yAxis: valueYAxis(),
  };
}

function luckyChartOption(list: CountryDashboardMetric[]): ChartOption {
  const option = baseChartOption('幸运礼物结构与利润率', list, [
    amountSeries('幸运礼物流水', 'luckyGiftTotalFlow', list),
    amountSeries('幸运礼物返奖', 'luckyGiftPayout', list),
    amountSeries('幸运礼物利润', 'luckyGiftProfit', list),
    {
      ...lineSeries('幸运礼物利润率', 'luckyGiftProfitRate', list),
      yAxisIndex: 1,
    },
  ]);
  return {
    ...option,
    yAxis: [
      valueYAxis(),
      {
        axisLabel: {
          color: '#94a3b8',
          formatter: '{value}%',
        },
        splitLine: { show: false },
        type: 'value',
      },
    ],
  };
}

async function renderVisualCharts() {
  if (!visualMode.value || loading.value) {
    return;
  }
  await nextTick();
  await new Promise<void>((resolve) => {
    requestAnimationFrame(() => resolve());
  });
  const list = chartRecords.value;
  if (list.length === 0) {
    return;
  }
  renderUserRechargeChart(baseChartOption('新增用户与充值', list, [
    amountSeries('新增用户', 'countryNewUser', list),
    amountSeries('新增充值', 'newUserRecharge', list),
    amountSeries('币商充值', 'dealerRecharge', list),
    amountSeries('MifaPay 充值', 'mifapayRecharge', list),
    amountSeries('Google 充值', 'googleRecharge', list),
    amountSeries('工资兑换', 'salaryExchange', list),
    amountSeries('总充值', 'totalRecharge', list),
  ]));
  renderFlowChart(baseChartOption('核心流水', list, [
    amountSeries('礼物消耗', 'giftConsume', list),
    amountSeries('幸运礼物流水', 'luckyGiftTotalFlow', list),
    amountSeries('游戏流水', 'gameTotalFlow', list),
  ]));
  renderProfitChart(baseChartOption('返奖与利润', list, [
    amountSeries('幸运礼物返奖', 'luckyGiftPayout', list),
    amountSeries('幸运礼物利润', 'luckyGiftProfit', list),
    amountSeries('游戏返奖', 'gamePayout', list),
    amountSeries('游戏利润', 'gameProfit', list),
  ]));
  renderLuckyChart(luckyChartOption(list));
}

watch(
  sysOriginOptions,
  (options) => {
    if (!query.sysOrigin && options[0]?.value) {
      query.sysOrigin = String(options[0].value);
    }
  },
  { immediate: true },
);

watch([records, visualMode], () => {
  renderVisualCharts();
});

watch(countryOptions, (options) => {
  const optionValues = new Set(options.map((item) => item.value));
  selectedCountryCodes.value = selectedCountryCodes.value.filter((code) =>
    optionValues.has(code),
  );
});

watch(activeTableTab, (value) => {
  if (value === 'game') {
    gameMetricPagination.current = 1;
    void loadGameMetrics();
  }
});

watch(selectedCountryCodes, () => {
  if (activeTableTab.value === 'game') {
    gameMetricPagination.current = 1;
    void loadGameMetrics();
    if (gameCountryRankOpen.value) {
      void loadGameCountryRank();
    }
  }
});

onMounted(() => {
  document.addEventListener('fullscreenchange', syncFullscreenState);
  loadDashboard();
});

onBeforeUnmount(() => {
  document.removeEventListener('fullscreenchange', syncFullscreenState);
});
</script>

<template>
  <div class="app-container-datav">
    <div
      ref="datavFullscreen"
      class="datav-shell" :class="[
        fullscreen ? 'datav-shell--fullscreen' : '',
      ]"
      @dblclick="screen"
    >
      <div class="datav-header">
        <div>
          <div class="datav-title">数据大屏</div>
          <div class="datav-subtitle">
            国家维度 · {{ currentTimezoneLabel }} · {{ dashboard?.computedAt || '-' }}
          </div>
        </div>
        <div class="datav-header__center">
          <Button
            :type="visualMode ? 'primary' : 'default'"
            @click="toggleVisualMode"
          >
            {{ visualMode ? '表格数据' : '可视化图表' }}
          </Button>
        </div>
        <Space wrap>
          <Button ghost @click="screen">
            {{ fullscreen ? '退出全屏' : '全屏' }}
          </Button>
        </Space>
      </div>

      <div v-if="canQuery" class="datav-content">
        <section class="panel filter-panel">
          <Space wrap>
            <SysOriginSelect
              v-model:value="query.sysOrigin"
              :options="sysOriginOptions"
              allow-clear
              placeholder="系统"
              style="width: 150px"
              @change="loadDashboard"
            />
            <Select
              v-model:value="query.statTimezone"
              :options="statTimezoneOptions"
              style="width: 170px"
              @change="handleTimezoneChange"
            />
            <Select
              v-model:value="query.periodType"
              :options="periodTypeOptions"
              style="width: 110px"
              @change="handlePeriodChange"
            />
            <DateRangePicker
              v-model:value="dateRange"
              allow-clear
              style="width: 260px"
              value-format="YYYY-MM-DD"
              @change="loadDashboard"
            />
            <Select
              v-model:value="selectedCountryCodes"
              :filter-option="filterCountry"
              :max-tag-count="2"
              :options="countryOptions"
              allow-clear
              mode="multiple"
              option-label-prop="label"
              placeholder="国家多选"
              show-search
              style="width: 300px"
            />
            <Select
              v-if="activeTableTab === 'game'"
              v-model:value="gameQuery.gameProvider"
              :options="gameProviderOptions"
              allow-clear
              placeholder="游戏厂商"
              style="width: 150px"
              @change="handleGameMetricFilterChange"
            />
            <Input
              v-if="activeTableTab === 'game'"
              v-model:value="gameQuery.gameKeyword"
              allow-clear
              placeholder="游戏 ID / 名称"
              style="width: 220px"
              @press-enter="handleGameMetricFilterChange"
            />
            <Button :loading="loading || gameMetricLoading" type="primary" @click="loadDashboard">
              查询
            </Button>
          </Space>
        </section>

        <section class="panel metric-section recharge-total-panel">
          <div class="metric-section__title">充值</div>
          <div class="metric-card-grid metric-card-grid--recharge">
            <div
              class="metric-card metric-card--clickable"
              role="button"
              tabindex="0"
              @click="openRechargeDetails"
              @keydown.enter="openRechargeDetails"
            >
              <div class="metric-card__label">{{ totalRechargeCard.label }}</div>
              <div class="metric-card__value">{{ totalRechargeCard.value }}</div>
            </div>
          </div>
        </section>

        <div class="metric-section-grid">
          <section class="panel metric-section">
            <div class="metric-section__title">幸运礼物 / 游戏</div>
            <div class="metric-card-grid">
              <div
                v-for="item in luckyGameCards"
                :key="item.label"
                class="metric-card"
              >
                <div class="metric-card__label">{{ item.label }}</div>
                <div class="metric-card__value">{{ item.value }}</div>
              </div>
            </div>
          </section>
          <section class="panel metric-section">
            <div class="metric-section__title">APP 数据</div>
            <div class="metric-card-grid metric-card-grid--app">
              <div
                v-for="item in appDataCards"
                :key="item.label"
                class="metric-card"
              >
                <div class="metric-card__label">{{ item.label }}</div>
                <div class="metric-card__value">{{ item.value }}</div>
              </div>
            </div>
          </section>
          <section class="panel metric-section">
            <div class="metric-section__title">ARPU</div>
            <div class="metric-card-grid metric-card-grid--arpu">
              <div
                v-for="item in arpuCards"
                :key="item.label"
                class="metric-card"
              >
                <div class="metric-card__label">{{ item.label }}</div>
                <div class="metric-card__value">{{ item.value }}</div>
              </div>
            </div>
          </section>
        </div>

        <section class="panel summary-table-panel">
          <Table
            :columns="summaryColumns"
            :data-source="summaryDataSource"
            :pagination="false"
            row-key="key"
            :scroll="{ x: summaryScrollX }"
            size="small"
          >
            <template #bodyCell="{ column, record }">
              <span class="number-cell summary-value-cell">
                {{ record[String(column.dataIndex)] }}
              </span>
            </template>
          </Table>
        </section>

        <section class="panel dashboard-panel">
          <Spin :spinning="loading">
            <template v-if="visualMode">
              <div v-if="chartRecords.length > 0" class="chart-grid">
                <section class="chart-card">
                  <EchartsUI ref="userRechargeChartRef" style="height: 340px; width: 100%" />
                </section>
                <section class="chart-card">
                  <EchartsUI ref="flowChartRef" style="height: 340px; width: 100%" />
                </section>
                <section class="chart-card">
                  <EchartsUI ref="profitChartRef" style="height: 340px; width: 100%" />
                </section>
                <section class="chart-card">
                  <EchartsUI ref="luckyChartRef" style="height: 340px; width: 100%" />
                </section>
              </div>
              <Empty v-else />
            </template>
            <template v-else>
              <Tabs v-model:active-key="activeTableTab" class="dashboard-tabs" size="small">
                <TabPane key="overview" tab="指标总览" />
                <TabPane key="recharge" tab="充值相关数据" />
                <TabPane key="game" tab="游戏数据" />
              </Tabs>
              <Table
                v-if="activeTableTab === 'game'"
                :columns="gameMetricColumns"
                :data-source="gameMetricRecords"
                :loading="gameMetricLoading"
                :pagination="{
                  current: gameMetricPagination.current,
                  pageSize: gameMetricPagination.pageSize,
                  showSizeChanger: true,
                  showTotal: showGameMetricTotal,
                  total: gameMetricPagination.total,
                }"
                :row-key="gameMetricRowKey"
                :scroll="{ x: gameTableScrollX }"
                size="small"
                @change="handleGameMetricTableChange"
              >
                <template #bodyCell="{ column, index, record }">
                  <template v-if="column.key === 'rank'">
                    <span class="number-cell">{{ gameMetricRank(index) }}</span>
                  </template>
                  <template v-else-if="column.key === 'gameName'">
                    <Button
                      class="game-link-button"
                      size="small"
                      type="link"
                      @click="openGameCountryRank(record)"
                    >
                      {{ gameDisplayText(record) }}
                    </Button>
                  </template>
                  <template
                    v-else-if="
                      gameMetricNumberColumns.has(String(column.dataIndex)) ||
                        gameMetricPercentColumns.has(String(column.dataIndex))
                    "
                  >
                    <span class="number-cell">
                      {{ gameMetricCellText(record, String(column.dataIndex)) }}
                    </span>
                  </template>
                </template>
              </Table>
              <Table
                v-else-if="records.length > 0"
                :columns="activeColumns"
                :data-source="records"
                :pagination="{ pageSize: 20, showSizeChanger: true }"
                :row-key="rowKey"
                :scroll="{ x: tableScrollX }"
                size="small"
              >
                <template #bodyCell="{ column, record }">
                  <template v-if="metricColumns.has(String(column.dataIndex))">
                    <Tooltip v-if="column.dataIndex === 'luckyGiftProfit'">
                      <template #title>
                        主播分成：{{ formatAmount(record.luckyGiftAnchorShare) }}
                      </template>
                      <span class="number-cell">
                        {{ tableCellText(record, String(column.dataIndex)) }}
                      </span>
                    </Tooltip>
                    <span v-else class="number-cell">
                      {{ tableCellText(record, String(column.dataIndex)) }}
                    </span>
                  </template>
                </template>
              </Table>
              <Empty v-else />
            </template>
          </Spin>
        </section>

        <div class="legacy-grid">
          <section class="panel">
            <div class="panel-title">购买用户</div>
            <LatestPurchaseList :sys-origin-codes="permissionsSysOriginCode" />
          </section>
          <section v-if="canPaidPreview" class="panel">
            <div class="panel-title">今日付费预览</div>
            <PurchaseCount />
          </section>
        </div>
      </div>

      <Modal
        v-model:open="rechargeDetailOpen"
        :footer="null"
        title="总充值明细"
        width="1180px"
      >
        <Table
          :columns="rechargeDetailColumns"
          :data-source="rechargeDetailRecords"
          :loading="rechargeDetailLoading"
          :pagination="{
            current: rechargeDetailPagination.current,
            pageSize: rechargeDetailPagination.pageSize,
            showSizeChanger: false,
            showTotal: showRechargeDetailTotal,
            total: rechargeDetailPagination.total,
          }"
          :row-key="rechargeDetailRowKey"
          :scroll="{ x: rechargeDetailScrollX }"
          size="small"
          @change="handleRechargeDetailTableChange"
        >
          <template #bodyCell="{ column, record }">
            <template v-if="column.key === 'sourceType'">
              {{ rechargeSourceName(record.sourceType) }}
            </template>
            <template v-else-if="column.key === 'user'">
              {{ rechargeDetailUserText(record) }}
            </template>
            <template v-else-if="column.key === 'country'">
              {{ rechargeDetailCountryText(record) }}
            </template>
            <template v-else-if="column.key === 'amount'">
              <span class="number-cell">{{ formatAmount(record.amount) }}</span>
            </template>
            <template v-else-if="column.key === 'coinQuantity'">
              <span class="number-cell">{{ formatAmount(record.coinQuantity) }}</span>
            </template>
          </template>
        </Table>
      </Modal>
      <Modal
        v-model:open="gameCountryRankOpen"
        :footer="null"
        :title="gameCountryRankTitle"
        width="860px"
      >
        <Table
          :columns="gameCountryRankColumns"
          :data-source="gameCountryRankRecords"
          :loading="gameCountryRankLoading"
          :pagination="false"
          :row-key="gameCountryRankRowKey"
          :scroll="{ x: gameCountryRankScrollX, y: 520 }"
          size="small"
        >
          <template #bodyCell="{ column, index, record }">
            <template v-if="column.key === 'rank'">
              <span class="number-cell">{{ index + 1 }}</span>
            </template>
            <template v-else-if="column.key === 'consumeAmount'">
              <span class="number-cell">{{ formatAmount(record.consumeAmount) }}</span>
            </template>
            <template v-else-if="column.key === 'countryName'">
              {{ record.countryName || record.countryCode || '-' }}
            </template>
          </template>
        </Table>
      </Modal>
    </div>
  </div>
</template>

<style scoped>
.app-container-datav {
  background: #111827;
  min-height: calc(100vh - 120px);
  overflow: hidden;
}

.datav-shell {
  background:
    linear-gradient(180deg, rgba(15, 23, 42, 0.96), rgba(17, 24, 39, 0.98)),
    #111827;
  color: #f8fafc;
  min-height: inherit;
  padding: 20px;
}

.datav-shell--fullscreen {
  min-height: 100vh;
  padding: 24px;
}

.datav-header {
  align-items: center;
  display: flex;
  gap: 16px;
  justify-content: space-between;
}

.datav-header__center {
  display: flex;
  flex: 1;
  justify-content: center;
}

.datav-title {
  color: #f8fafc;
  font-size: 28px;
  font-weight: 700;
  line-height: 1.3;
}

.datav-subtitle {
  color: #94a3b8;
  font-size: 13px;
  line-height: 1.8;
}

.datav-content {
  display: grid;
  gap: 16px;
  margin-top: 18px;
}

.panel {
  background: rgba(30, 41, 59, 0.82);
  border: 1px solid rgba(148, 163, 184, 0.18);
  border-radius: 8px;
  overflow: hidden;
}

.filter-panel {
  padding: 14px;
}

.metric-section-grid {
  display: grid;
  gap: 16px;
  grid-template-columns: minmax(0, 3fr) minmax(260px, 2fr) minmax(260px, 2fr);
}

.metric-section {
  padding: 14px;
}

.metric-section__title {
  color: #2dd4bf;
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 12px;
}

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

.metric-card-grid--app {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.metric-card-grid--arpu {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.metric-card-grid--recharge {
  grid-template-columns: minmax(180px, 260px);
}

.metric-card {
  background: #111827;
  border: 1px solid rgba(45, 212, 191, 0.18);
  border-radius: 8px;
  min-height: 76px;
  padding: 12px;
}

.metric-card--clickable {
  cursor: pointer;
  transition: border-color 0.16s ease, transform 0.16s ease;
}

.metric-card--clickable:focus-visible,
.metric-card--clickable:hover {
  border-color: rgba(45, 212, 191, 0.72);
  outline: none;
  transform: translateY(-1px);
}

.metric-card__label {
  color: #cbd5e1;
  font-size: 12px;
  line-height: 1.4;
}

.metric-card__value {
  color: #f8fafc;
  font-size: 20px;
  font-weight: 700;
  line-height: 1.6;
  overflow-wrap: anywhere;
}

.summary-table-panel {
  width: 100%;
}

.dashboard-panel {
  padding: 12px;
}

.dashboard-tabs {
  margin-bottom: 10px;
}

.dashboard-tabs :deep(.ant-tabs-nav) {
  margin-bottom: 8px;
}

.dashboard-tabs :deep(.ant-tabs-tab) {
  color: #cbd5e1;
}

.dashboard-tabs :deep(.ant-tabs-tab-active .ant-tabs-tab-btn) {
  color: #2dd4bf;
}

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

.chart-card {
  background: #111827;
  border: 1px solid rgba(148, 163, 184, 0.14);
  border-radius: 8px;
  min-height: 360px;
  padding: 10px;
}

.number-cell {
  color: #e2e8f0;
  font-variant-numeric: tabular-nums;
}

.game-link-button {
  height: auto;
  padding: 0;
  white-space: normal;
}

.game-link-button :deep(span) {
  overflow-wrap: anywhere;
  text-align: left;
}

.summary-value-cell {
  display: block;
  font-weight: 700;
  text-align: right;
}

.legacy-grid {
  display: grid;
  gap: 16px;
  grid-template-columns: minmax(300px, 1fr) minmax(300px, 1fr);
}

.panel-title {
  color: #2dd4bf;
  font-size: 18px;
  font-weight: 700;
  padding: 16px 16px 8px;
}

.panel :deep(.ant-table) {
  background: transparent;
}

.panel :deep(.ant-table-cell) {
  background: #111827;
  color: #e2e8f0;
}

.panel :deep(.ant-table-thead > tr > th) {
  background: #1f2937;
  color: #f8fafc;
}

.panel :deep(.ant-empty-description) {
  color: #cbd5e1;
}

@media (max-width: 768px) {
  .app-container-datav {
    min-height: calc(100vh - 88px);
  }

  .datav-shell {
    padding: 14px;
  }

  .datav-header {
    align-items: flex-start;
    flex-direction: column;
  }

  .datav-header__center {
    justify-content: flex-start;
    width: 100%;
  }

  .datav-title {
    font-size: 24px;
  }

  .chart-grid,
  .legacy-grid {
    grid-template-columns: 1fr;
  }

  .metric-section-grid,
  .metric-card-grid,
  .metric-card-grid--app,
  .metric-card-grid--arpu {
    grid-template-columns: 1fr;
  }
}
</style>
