<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter, RouterLink } from 'vue-router';
import { PARLAY_MIN_LEGS, PARLAY_MAX_LEGS } from '@thebet365/shared';
import { useBetSlipStore, type ParlaySlipError, type SlipItem, type SlipMode } from '../../stores/betSlip';
import { useAuthStore } from '../../stores/auth';
import { formatMoney, parseAmount } from '../../utils/localeDisplay';
import api from '../../api';
import { usePlayerProfile } from '../../composables/usePlayerProfile';
import BetSuccessOverlay from '../BetSuccessOverlay.vue';
import ConfirmDialog from '../ConfirmDialog.vue';
import GoldSpinner from '../GoldSpinner.vue';
import { type BetHistoryItem } from '../BetHistoryCard.vue';
import { buildBetPlaceConfirmMessage } from '../../utils/betPlaceConfirmMessage';
import { useDesktopBetLocate } from '../../composables/useDesktopBetLocate';

type PanelTab = SlipMode | 'history';

const { t, locale } = useI18n();
const route = useRoute();
const router = useRouter();
const slip = useBetSlipStore();
const auth = useAuthStore();
const { refreshProfile } = usePlayerProfile();
const { requestLocate } = useDesktopBetLocate();

const activeTab = ref<PanelTab>('single');
const transitionName = ref('list-slide');
const historyScope = ref<'all' | 'match'>('all');
const historyItems = ref<BetHistoryItem[]>([]);
const historyTotal = ref(0);
const historyPage = ref(1);
const historyPageSize = 15;
const historyLoading = ref(false);
const historyInitialLoading = ref(false);
const loading = ref(false);
const balanceLoading = ref(false);
const balance = ref<number | null>(null);
const error = ref('');
const success = ref('');
const showSuccess = ref(false);
const showPlaceConfirm = ref(false);
const placeConfirmMessage = ref('');
const pendingSingleItem = ref<SlipItem | null>(null);
const MIN_STAKE = 5;
const MAX_STAKE_INTEGER_LENGTH = 9;
const stakeInput = ref('');
const itemStakeInputs = ref<Record<string, string>>({});
const ODDS_POLL_MS = 5000;

type OddsDelta = {
  oldOdds: number;
  newOdds: number;
  newVersion: string;
  suspended: boolean;
};

interface SelectionOddsRow {
  id: string;
  odds: string;
  oddsVersion: string;
  status: string;
  marketStatus: string;
  marketShowOnPlayer: boolean;
  matchStatus: string;
}

const oddsDeltas = ref<Record<string, OddsDelta>>({});
let oddsPollTimer: ReturnType<typeof setInterval> | null = null;

const currentMatchId = computed(() => {
  const match = route.path.match(/^\/match\/([^/]+)/);
  if (match) return match[1];
  const outright = route.path.match(/^\/outright\/([^/]+)/);
  return outright ? outright[1] : '';
});

const isHomePage = computed(() => route.path === '/');

const showMatchHistoryFilter = computed(() => Boolean(currentMatchId.value));

const singleSlipItems = computed<SlipItem[]>(() => {
  if (slip.singleCartItems.length) return slip.singleCartItems;
  return slip.singleItem ? [slip.singleItem] : [];
});

const activeItems = computed<SlipItem[]>(() => {
  if (activeTab.value === 'history') return [];
  if (activeTab.value === 'parlay') return slip.parlayItems;
  return singleSlipItems.value;
});

const historyHasMore = computed(
  () => historyItems.value.length < historyTotal.value,
);

const activeCount = computed(() => activeItems.value.length);

function effectiveOdds(item: SlipItem) {
  return oddsDeltas.value[item.selectionId]?.newOdds ?? item.odds;
}

const activeTotalOdds = computed(() =>
  activeItems.value.reduce((acc, item) => acc * effectiveOdds(item), 1),
);
const activeEstimatedReturn = computed(() => {
  if (!activeItems.value.length) return 0;
  if (activeTab.value === 'parlay') {
    if (!Number.isFinite(slip.stake) || slip.stake <= 0) return 0;
    return slip.stake * activeTotalOdds.value;
  }
  return activeItems.value.reduce((acc, item) => {
    const s = slip.getItemStake(item.selectionId);
    if (!Number.isFinite(s) || s <= 0) return acc;
    return acc + s * effectiveOdds(item);
  }, 0);
});

const activeTotalStake = computed(() => {
  if (activeTab.value === 'parlay') return Number(slip.stake) || 0;
  if (activeTab.value !== 'single') return 0;
  return singleSlipItems.value.reduce((acc, item) => acc + slip.getItemStake(item.selectionId), 0);
});

const hasSuspendedSelections = computed(() =>
  Object.values(oddsDeltas.value).some((delta) => delta.suspended),
);

const hasPendingOddsChanges = computed(() =>
  Object.values(oddsDeltas.value).some((delta) => !delta.suspended),
);

const oddsWarningText = computed(() => {
  if (hasSuspendedSelections.value) return t('bet.odds_suspended');
  if (hasPendingOddsChanges.value) return t('bet.odds_changed');
  return '';
});

const submitButtonLabel = computed(() => {
  if (loading.value) return t('bet.placing');
  if (hasPendingOddsChanges.value) return t('bet.accept_changes_place');
  if (activeTab.value === 'single') return t('bet.slip_place_all');
  return t('bet.place_bet_short');
});

const canSubmitWithOdds = computed(() => canSubmitActive.value && !hasSuspendedSelections.value);

const canSubmitActive = computed(() => {
  if (activeTab.value === 'parlay') {
    return slip.parlayItems.length >= PARLAY_MIN_LEGS && slip.parlayItems.length <= PARLAY_MAX_LEGS;
  }
  if (activeTab.value !== 'single') return false;
  return singleSlipItems.value.length > 0 && singleSlipItems.value.every((item) => item.allowSingle !== false);
});

const singleParlayOnlyHint = computed(
  () =>
    slip.mode === 'single' &&
    Boolean(slip.singleItem) &&
    slip.singleItem!.allowSingle === false &&
    slip.singleItem!.allowParlay !== false,
);

const balanceText = computed(() => {
  if (balanceLoading.value) return t('bet.loading');
  if (balance.value == null) return '--';
  return formatMoney(balance.value, locale.value);
});

const stakeText = computed(() => formatMoney(activeTotalStake.value, locale.value));
const estimatedReturnText = computed(() => formatMoney(activeEstimatedReturn.value, locale.value));
const totalOddsText = computed(() => activeTotalOdds.value.toFixed(4).replace(/0+$/, '').replace(/\.$/, ''));

const parlayWarning = computed(() => {
  if (activeTab.value !== 'parlay') return '';
  if (slip.lastParlayError) return parlayErrorMessage(slip.lastParlayError);
  if (slip.parlayItems.length > 0 && slip.parlayItems.length < PARLAY_MIN_LEGS) {
    return t('bet.parlay_need_more');
  }
  return '';
});

const singleInParlay = computed(() => {
  const item = slip.singleItem;
  return Boolean(item && slip.parlayItems.some((leg) => leg.selectionId === item.selectionId));
});

const showFooterParlayAction = computed(() => slip.mode === 'single' && Boolean(slip.singleItem));

function genId() {
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
}

function selectTab(tab: PanelTab) {
  transitionName.value = '';
  activeTab.value = tab;
  error.value = '';
  if (tab === 'history') {
    if (!auth.token) {
      auth.showLoginPrompt(route.fullPath);
      activeTab.value = slip.mode;
      nextTick(() => {
        transitionName.value = 'list-slide';
      });
      return;
    }
    if (showMatchHistoryFilter.value) {
      historyScope.value = 'match';
    }
    void loadHistory(true);
    nextTick(() => {
      transitionName.value = 'list-slide';
    });
    return;
  }
  slip.setMode(tab);
  syncStakeInputFromSlip();
  nextTick(() => {
    transitionName.value = 'list-slide';
  });
}

function selectHistoryScope(scope: 'all' | 'match') {
  if (historyScope.value === scope) return;
  historyScope.value = scope;
  void loadHistory(true);
}

async function loadHistory(reset = false) {
  if (!auth.token || activeTab.value !== 'history') return;
  if (historyLoading.value) return;
  if (reset) {
    historyPage.value = 1;
    historyInitialLoading.value = true;
  }
  historyLoading.value = true;
  try {
    const params: Record<string, unknown> = {
      page: historyPage.value,
      pageSize: historyPageSize,
    };
    if (historyScope.value === 'match' && currentMatchId.value) {
      params.matchId = currentMatchId.value;
    }
    const { data } = await api.get('/player/bets', { params });
    const result = data.data ?? { items: [], total: 0 };
    const items = (result.items ?? []) as BetHistoryItem[];
    historyTotal.value = result.total ?? 0;
    historyItems.value = reset ? items : [...historyItems.value, ...items];
  } catch {
    if (reset) {
      historyItems.value = [];
      historyTotal.value = 0;
    }
  } finally {
    historyLoading.value = false;
    historyInitialLoading.value = false;
  }
}

function loadMoreHistory() {
  if (!historyHasMore.value || historyLoading.value) return;
  historyPage.value += 1;
  void loadHistory(false);
}

function historyStatusLabel(status: string) {
  const s = status.toUpperCase();
  if (s === 'WON' || s === 'WIN') return t('history.status_won');
  if (s === 'LOST' || s === 'LOSE') return t('history.status_lost');
  if (s === 'PUSH' || s === 'VOID' || s === 'CANCELLED') return t('history.status_push');
  return t('history.status_pending');
}

function historyStatusClass(status: string) {
  const s = status.toUpperCase();
  if (s === 'WON' || s === 'WIN') return 'hist-won';
  if (s === 'LOST' || s === 'LOSE') return 'hist-lost';
  if (s === 'PUSH' || s === 'VOID' || s === 'CANCELLED') return 'hist-push';
  return 'hist-pending';
}

function historyPickLabel(bet: BetHistoryItem) {
  if (bet.betType === 'PARLAY' || bet.isParlay) {
    const n = bet.legCount ?? bet.legs?.length ?? 0;
    return n > 0 ? t('history.parlay_title', { n }) : t('history.parlay_league');
  }
  return bet.pickLabel || bet.matchTitle || '-';
}

function removeItem(id: string) {
  slip.removeItem(id);
  error.value = '';
}

function locateSlipItem(item: SlipItem) {
  const isOutright = item.marketType === 'OUTRIGHT_WINNER';
  const targetPath = isOutright ? `/outright/${item.matchId}` : `/match/${item.matchId}`;
  requestLocate({
    matchId: item.matchId,
    selectionId: item.selectionId,
    marketId: item.marketId,
    marketType: item.marketType,
  });
  if (route.path !== targetPath) {
    void router.push(targetPath);
  }
}

function clearActiveSlip() {
  error.value = '';
  oddsDeltas.value = {};
  if (activeTab.value === 'parlay') {
    slip.clearParlay();
  } else if (activeTab.value === 'single') {
    slip.clearSingle();
  }
  syncStakeInputFromSlip();
}

function stakeAmountToInput(amount: number) {
  if (!Number.isFinite(amount) || amount <= 0) return '';
  const rounded = Math.round(amount * 100) / 100;
  return Number.isInteger(rounded)
    ? String(rounded)
    : rounded.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
}

function sanitizeStakeInput(raw: string) {
  const onlyAmountChars = raw.replace(/[^\d.]/g, '');
  const [integerRaw, ...fractionParts] = onlyAmountChars.split('.');
  const integerPart = integerRaw.replace(/^0+(?=\d)/, '').slice(0, MAX_STAKE_INTEGER_LENGTH);
  if (!fractionParts.length) return integerPart;
  const fractionPart = fractionParts.join('').slice(0, 2);
  return `${integerPart || '0'}.${fractionPart}`;
}

function commitStakeInput(raw: string) {
  const clean = sanitizeStakeInput(raw);
  stakeInput.value = clean;
  const amount = Number.parseFloat(clean);
  slip.stake = Number.isFinite(amount) ? amount : 0;
}

function syncItemStakeInputs(items: SlipItem[]) {
  const next: Record<string, string> = {};
  for (const item of items) {
    slip.initItemStake(item.selectionId);
    next[item.selectionId] = stakeAmountToInput(slip.getItemStake(item.selectionId));
  }
  itemStakeInputs.value = next;
}

function itemStakeInputValue(selectionId: string) {
  if (!(selectionId in itemStakeInputs.value)) {
    slip.initItemStake(selectionId);
    itemStakeInputs.value = {
      ...itemStakeInputs.value,
      [selectionId]: stakeAmountToInput(slip.getItemStake(selectionId)),
    };
  }
  return itemStakeInputs.value[selectionId];
}

function onItemStakeInput(selectionId: string, event: Event) {
  const clean = sanitizeStakeInput((event.target as HTMLInputElement).value);
  itemStakeInputs.value = { ...itemStakeInputs.value, [selectionId]: clean };
  const amount = Number.parseFloat(clean);
  slip.setItemStake(selectionId, Number.isFinite(amount) ? amount : 0);
}

function clearItemStakeInput(selectionId: string) {
  itemStakeInputs.value = { ...itemStakeInputs.value, [selectionId]: '' };
  slip.setItemStake(selectionId, 0);
}

function addItemStake(selectionId: string, amount: number) {
  const next = (slip.getItemStake(selectionId) || 0) + amount;
  const normalized = Math.round(Math.max(MIN_STAKE, next) * 100) / 100;
  slip.setItemStake(selectionId, normalized);
  itemStakeInputs.value = {
    ...itemStakeInputs.value,
    [selectionId]: stakeAmountToInput(normalized),
  };
}

function setItemMaxStake(selectionId: string) {
  if (balance.value == null || balance.value <= 0) return;
  const normalized = Math.round(balance.value * 100) / 100;
  slip.setItemStake(selectionId, normalized);
  itemStakeInputs.value = {
    ...itemStakeInputs.value,
    [selectionId]: stakeAmountToInput(normalized),
  };
}

function itemEstReturn(item: SlipItem) {
  const s = slip.getItemStake(item.selectionId);
  if (!Number.isFinite(s) || s <= 0) return 0;
  return s * effectiveOdds(item);
}

function syncStakeInputFromSlip() {
  stakeInput.value = stakeAmountToInput(Number(slip.stake) || 0);
  if (activeTab.value === 'single') {
    syncItemStakeInputs(singleSlipItems.value);
  }
}

function onStakeInput(event: Event) {
  commitStakeInput((event.target as HTMLInputElement).value);
}

function clearStakeInput() {
  commitStakeInput('');
}

function setStake(amount: number, enforceMinimum = true) {
  if (!Number.isFinite(amount)) return;
  const normalized = Math.round((enforceMinimum ? Math.max(MIN_STAKE, amount) : amount) * 100) / 100;
  slip.stake = normalized;
  stakeInput.value = stakeAmountToInput(normalized);
}

function addStake(amount: number) {
  setStake((Number(slip.stake) || 0) + amount);
}

function setMaxStake() {
  if (balance.value != null && balance.value > 0) setStake(balance.value, false);
}

function stopOddsPolling() {
  if (oddsPollTimer) {
    clearInterval(oddsPollTimer);
    oddsPollTimer = null;
  }
  oddsDeltas.value = {};
}

function acceptPendingOdds() {
  for (const [selectionId, delta] of Object.entries(oddsDeltas.value)) {
    if (!delta.suspended) {
      slip.updateSelectionOdds(selectionId, delta.newOdds, delta.newVersion);
    }
  }
  oddsDeltas.value = {};
}

function acceptPendingOddsForSelection(selectionId: string) {
  const delta = oddsDeltas.value[selectionId];
  if (!delta || delta.suspended) return;
  slip.updateSelectionOdds(selectionId, delta.newOdds, delta.newVersion);
  const next = { ...oddsDeltas.value };
  delete next[selectionId];
  oddsDeltas.value = next;
}

function isItemSuspended(item: SlipItem) {
  return Boolean(oddsDeltaFor(item.selectionId)?.suspended);
}

function canPlaceSingleItem(item: SlipItem) {
  if (item.allowSingle === false) return false;
  if (isItemSuspended(item)) return false;
  return slip.getItemStake(item.selectionId) >= MIN_STAKE;
}

function validateSingleItemPlace(item: SlipItem): boolean {
  if (!auth.token) {
    auth.showLoginPrompt();
    return false;
  }
  if (item.allowSingle === false) {
    error.value = t('bet.slip_parlay_only_hint');
    return false;
  }
  const itemStake = slip.getItemStake(item.selectionId);
  if (itemStake < MIN_STAKE) {
    error.value = t('bet.slip_min_error', { amount: MIN_STAKE });
    return false;
  }
  if (balance.value != null && itemStake > balance.value) {
    error.value = t('bet.outright_insufficient');
    return false;
  }
  if (isItemSuspended(item)) {
    error.value = t('bet.odds_suspended');
    return false;
  }
  return true;
}

function onPlaceSingleItemClick(item: SlipItem) {
  error.value = '';
  if (!validateSingleItemPlace(item)) return;
  if (oddsDeltaFor(item.selectionId) && !isItemSuspended(item)) {
    acceptPendingOddsForSelection(item.selectionId);
  }
  pendingSingleItem.value = item;
  const stake = slip.getItemStake(item.selectionId);
  placeConfirmMessage.value = buildBetPlaceConfirmMessage(t, {
    mode: 'single',
    items: [item],
    totalStake: stake,
    totalReturn: stake * effectiveOdds(item),
    formatMoney: (amount) => formatMoney(amount, locale.value),
    getStake: () => stake,
    getOdds: () => effectiveOdds(item),
  });
  showPlaceConfirm.value = true;
}

async function pollSelectionsOdds() {
  const items = activeItems.value;
  if (!items.length) return;

  try {
    const ids = items.map((item) => item.selectionId).join(',');
    const { data } = await api.get('/player/selections/odds', { params: { ids } });
    const rows: SelectionOddsRow[] = data.data?.items ?? [];
    const rowMap = new Map(rows.map((row) => [row.id, row]));
    const next: Record<string, OddsDelta> = {};

    for (const item of items) {
      const row = rowMap.get(item.selectionId);
      if (!row) continue;

      const suspended =
        row.status !== 'OPEN' ||
        row.marketStatus !== 'OPEN' ||
        row.marketShowOnPlayer === false ||
        row.matchStatus !== 'PUBLISHED';
      const newOdds = parseFloat(row.odds);
      const versionChanged = row.oddsVersion !== item.oddsVersion;
      const oddsChanged = Number.isFinite(newOdds) && Math.abs(newOdds - item.odds) > 0.0001;

      if (suspended || versionChanged || oddsChanged) {
        const existing = oddsDeltas.value[item.selectionId];
        next[item.selectionId] = {
          oldOdds: existing?.oldOdds ?? item.odds,
          newOdds: Number.isFinite(newOdds) ? newOdds : item.odds,
          newVersion: row.oddsVersion,
          suspended,
        };
      }
    }

    oddsDeltas.value = next;
  } catch {
    /* silent retry */
  }
}

function startOddsPolling() {
  stopOddsPolling();
  void pollSelectionsOdds();
  oddsPollTimer = setInterval(() => {
    void pollSelectionsOdds();
  }, ODDS_POLL_MS);
}

function oddsDeltaFor(selectionId: string) {
  return oddsDeltas.value[selectionId];
}

function oddsTrendClass(delta: OddsDelta) {
  if (delta.suspended) return 'odds-suspended';
  return delta.newOdds >= delta.oldOdds ? 'odds-up' : 'odds-down';
}

function singlesForSubmit() {
  return [...singleSlipItems.value];
}

function validatePlaceBet(): boolean {
  if (!activeItems.value.length) return false;
  if (!auth.token) {
    auth.showLoginPrompt();
    return false;
  }
  if (activeTab.value === 'parlay') {
    if (slip.stake < MIN_STAKE) {
      error.value = t('bet.slip_min_error', { amount: MIN_STAKE });
      return false;
    }
    if (balance.value != null && slip.stake > balance.value) {
      error.value = t('bet.outright_insufficient');
      return false;
    }
  } else {
    const singles = singlesForSubmit();
    let totalStake = 0;
    for (const item of singles) {
      const itemStake = slip.getItemStake(item.selectionId);
      if (itemStake < MIN_STAKE) {
        error.value = t('bet.slip_min_error', { amount: MIN_STAKE });
        return false;
      }
      totalStake += itemStake;
    }
    if (balance.value != null && totalStake > balance.value) {
      error.value = t('bet.outright_insufficient');
      return false;
    }
  }
  if (activeTab.value === 'parlay' && !canSubmitActive.value) {
    error.value = slip.parlayItems.length > PARLAY_MAX_LEGS
      ? t('bet.parlay_max_legs')
      : t('bet.parlay_need_more');
    return false;
  }
  if (hasSuspendedSelections.value) {
    error.value = t('bet.odds_suspended');
    return false;
  }
  return true;
}

function onPlaceBetClick() {
  error.value = '';
  if (!validatePlaceBet()) return;
  if (hasPendingOddsChanges.value) {
    acceptPendingOdds();
  }
  pendingSingleItem.value = null;
  const singles = singlesForSubmit();
  placeConfirmMessage.value = buildBetPlaceConfirmMessage(t, {
    mode: activeTab.value === 'parlay' ? 'parlay' : 'single',
    items: activeTab.value === 'parlay' ? [...slip.parlayItems] : singles,
    totalStake: activeTotalStake.value,
    totalReturn: activeEstimatedReturn.value,
    totalOdds: activeTab.value === 'parlay' ? activeTotalOdds.value : undefined,
    formatMoney: (amount) => formatMoney(amount, locale.value),
    getStake: (item) => slip.getItemStake(item.selectionId),
    getOdds: (item) => effectiveOdds(item),
  });
  showPlaceConfirm.value = true;
}

async function executePlaceSingleItem(item: SlipItem) {
  loading.value = true;
  error.value = '';
  success.value = '';

  try {
    await api.post('/player/bets/single', {
      selectionId: item.selectionId,
      oddsVersion: item.oddsVersion,
      stake: slip.getItemStake(item.selectionId),
      requestId: genId(),
    });
    slip.removeItem(item.selectionId);
    success.value = t('bet.place_success');
    showSuccess.value = true;
    await Promise.all([loadBalance(), refreshProfile()]);
    setTimeout(() => {
      if (showSuccess.value) onSuccessDone();
    }, 2200);
  } catch (e: unknown) {
    error.value =
      (e as { response?: { data?: { error?: string } } })?.response?.data?.error ||
      t('bet.place_failed');
  } finally {
    loading.value = false;
  }
}

async function executePlaceBet() {
  loading.value = true;
  error.value = '';
  success.value = '';

  try {
    if (activeTab.value === 'parlay') {
      await api.post('/player/bets/parlay', {
        legs: slip.parlayItems.map((item) => ({
          selectionId: item.selectionId,
          oddsVersion: item.oddsVersion,
        })),
        stake: slip.stake,
        requestId: genId(),
      });
      slip.clearParlay();
    } else {
      for (const item of singlesForSubmit()) {
        await api.post('/player/bets/single', {
          selectionId: item.selectionId,
          oddsVersion: item.oddsVersion,
          stake: slip.getItemStake(item.selectionId),
          requestId: genId(),
        });
      }
      slip.clearSingle();
    }
    success.value = t('bet.place_success');
    showSuccess.value = true;
    if (activeTab.value === 'history') {
      void loadHistory(true);
    }
    await Promise.all([loadBalance(), refreshProfile()]);
    setTimeout(() => {
      if (showSuccess.value) onSuccessDone();
    }, 2200);
  } catch (e: unknown) {
    error.value =
      (e as { response?: { data?: { error?: string } } })?.response?.data?.error ||
      t('bet.place_failed');
  } finally {
    loading.value = false;
  }
}

async function confirmPlaceBet() {
  showPlaceConfirm.value = false;
  const singleItem = pendingSingleItem.value;
  pendingSingleItem.value = null;
  if (singleItem) {
    await executePlaceSingleItem(singleItem);
    return;
  }
  await executePlaceBet();
}

function onSuccessDone() {
  showSuccess.value = false;
  error.value = '';
  success.value = '';
  syncStakeInputFromSlip();
}

async function loadBalance() {
  if (!auth.token) {
    balance.value = null;
    return;
  }
  balanceLoading.value = true;
  try {
    const { data } = await api.get('/player/profile');
    balance.value = parseAmount(data.data?.wallet?.availableBalance);
  } catch {
    balance.value = null;
  } finally {
    balanceLoading.value = false;
  }
}

function parlayErrorMessage(reason: ParlaySlipError) {
  if (reason === 'MAX_LEGS') return t('bet.parlay_max_legs');
  if (reason === 'QUARTER_LINE') return t('bet.parlay_block_quarter');
  if (reason === 'OUTRIGHT') return t('bet.parlay_block_outright');
  if (reason === 'NOT_ALLOWED') return t('bet.parlay_block_not_allowed');
  if (reason === 'SAME_MATCH') return t('bet.slip_parlay_same_match');
  return t('bet.parlay_block_not_allowed');
}

function addItemToParlay(item: SlipItem) {
  if (!auth.token) {
    auth.showLoginPrompt(route.fullPath);
    return;
  }
  if (slip.parlayItems.some((leg) => leg.selectionId === item.selectionId)) {
    activeTab.value = 'parlay';
    slip.setMode('parlay');
    error.value = '';
    return;
  }
  const err = slip.addParlayLeg(item);
  activeTab.value = 'parlay';
  slip.setMode('parlay');
  if (err) {
    error.value = parlayErrorMessage(err);
    return;
  }
  error.value = '';
}

function isItemInParlay(item: SlipItem) {
  return slip.parlayItems.some((leg) => leg.selectionId === item.selectionId);
}

function parlayActionLabel(item: SlipItem) {
  return isItemInParlay(item) ? t('bet.slip_parlay_added_short') : t('bet.add_to_parlay');
}

function addCurrentToParlay() {
  if (singleInParlay.value) {
    activeTab.value = 'parlay';
    slip.setMode('parlay');
    error.value = '';
    return;
  }
  const err = slip.addSingleToParlay();
  activeTab.value = 'parlay';
  slip.setMode('parlay');
  if (err) {
    error.value = parlayErrorMessage(err);
    return;
  }
  error.value = '';
}

watch(
  () => activeItems.value.map((item) => item.selectionId).join(','),
  (ids) => {
    if (ids) {
      if (activeTab.value === 'single') syncItemStakeInputs(singleSlipItems.value);
      startOddsPolling();
    } else {
      itemStakeInputs.value = {};
      stopOddsPolling();
    }
  },
  { immediate: true },
);

watch(
  () => slip.mode,
  (mode) => {
    if (activeTab.value !== 'history') {
      transitionName.value = '';
      activeTab.value = mode;
      nextTick(() => {
        transitionName.value = 'list-slide';
      });
    }
  },
);

watch(showMatchHistoryFilter, (show) => {
  if (!show && historyScope.value === 'match') {
    historyScope.value = 'all';
    if (activeTab.value === 'history') void loadHistory(true);
  }
});

watch(currentMatchId, (id, prev) => {
  if (activeTab.value !== 'history') return;
  if (id && id !== prev && historyScope.value === 'match') {
    void loadHistory(true);
  }
  if (!id && historyScope.value === 'match') {
    historyScope.value = 'all';
    void loadHistory(true);
  }
});

watch(
  () => auth.token,
  (token) => {
    if (!token && activeTab.value === 'history') {
      activeTab.value = slip.mode;
      historyItems.value = [];
      historyTotal.value = 0;
    }
  },
);

onMounted(() => {
  activeTab.value = slip.mode;
  if (activeTab.value === 'single' && !slip.singleItem && slip.parlayItems.length) {
    activeTab.value = 'parlay';
    slip.setMode('parlay');
  }
  syncStakeInputFromSlip();
  void loadBalance();
});

onUnmounted(() => {
  stopOddsPolling();
});
</script>

<template>
  <div class="betslip-panel">
    <div class="panel-head">
      <h3>{{ t('bet.bet_slip') || '投注单' }}</h3>
    </div>

    <div class="panel-tabs">
      <button
        type="button"
        class="tab-btn"
        :class="{ active: activeTab === 'single' }"
        @click="selectTab('single')"
      >
        {{ t('bet.slip_tab_single') }}
        <span v-if="slip.singleCount" class="badge">{{ slip.singleCount }}</span>
      </button>
      <button
        type="button"
        class="tab-btn"
        :class="{ active: activeTab === 'parlay' }"
        @click="selectTab('parlay')"
      >
        {{ t('bet.slip_tab_parlay') }}
        <span v-if="slip.parlayCount" class="badge">{{ slip.parlayCount }}</span>
      </button>
      <button
        type="button"
        class="tab-btn"
        :class="{ active: activeTab === 'history' }"
        @click="selectTab('history')"
      >
        {{ t('bet.slip_tab_history') }}
      </button>
    </div>

    <div v-if="activeTab === 'history' && showMatchHistoryFilter" class="history-scope-bar">
      <button
        type="button"
        class="scope-chip"
        :class="{ active: historyScope === 'all' }"
        @click="selectHistoryScope('all')"
      >
        {{ t('bet.slip_history_all') }}
      </button>
      <button
        type="button"
        class="scope-chip"
        :class="{ active: historyScope === 'match' }"
        @click="selectHistoryScope('match')"
      >
        {{ t('bet.slip_history_match') }}
      </button>
    </div>

    <!-- Warnings -->
    <p v-if="activeTab !== 'history' && oddsWarningText" class="odds-warning">{{ oddsWarningText }}</p>

    <!-- Body -->
    <div class="panel-body" :class="{ 'no-scrollbar': isHomePage }">
      <template v-if="activeTab === 'history'">
        <div v-if="historyInitialLoading" class="history-loading">
          <GoldSpinner :size="28" />
        </div>
        <div v-else-if="!historyItems.length" class="empty-state">
          <svg class="empty-icon" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2zm0-4H7V7h10v2zm0 8H7v-2h10v2z" fill="currentColor"/>
          </svg>
          <p>{{ t('history.empty') }}</p>
        </div>
        <div v-else class="history-list">
          <RouterLink
            v-for="bet in historyItems"
            :key="bet.betNo"
            :to="`/bets/${bet.betNo}`"
            class="history-card"
          >
            <div class="history-card-top">
              <span class="history-pick">{{ historyPickLabel(bet) }}</span>
              <span class="history-status" :class="historyStatusClass(bet.status)">
                {{ historyStatusLabel(bet.status) }}
              </span>
            </div>
            <div v-if="historyScope === 'all' && bet.matchTitle" class="history-match">
              {{ bet.matchTitle }}
            </div>
            <div class="history-card-foot">
              <span class="history-stake">{{ t('history.stake') }} {{ formatMoney(bet.stake, locale) }}</span>
              <span class="history-return" :class="historyStatusClass(bet.status)">
                {{ formatMoney(bet.actualReturn || bet.potentialReturn, locale) }}
              </span>
            </div>
            <div class="history-meta">
              <span>{{ bet.betNo }}</span>
              <span>{{ bet.placedAt ? new Date(bet.placedAt).toLocaleString(locale) : '' }}</span>
            </div>
          </RouterLink>
        </div>
        <button
          v-if="historyHasMore && !historyInitialLoading"
          type="button"
          class="history-load-more"
          :disabled="historyLoading"
          @click="loadMoreHistory"
        >
          {{ historyLoading ? t('bet.loading') : t('bet.slip_history_load_more') }}
        </button>
      </template>

      <template v-else>
      <div v-if="!activeItems.length" class="empty-state">
        <svg class="empty-icon" viewBox="0 0 24 24" aria-hidden="true">
          <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2zm0-4H7V7h10v2zm0 8H7v-2h10v2z" fill="currentColor"/>
        </svg>
        <p>{{ activeTab === 'parlay' ? t('bet.slip_parlay_empty_hint') : t('bet.slip_empty_hint') }}</p>
      </div>

      <TransitionGroup v-else :name="transitionName" tag="div" class="items-list">
        <p v-if="activeTab === 'parlay' && parlayWarning" class="panel-warning">{{ parlayWarning }}</p>
        <div
          v-for="item in activeItems"
          :key="item.selectionId"
          class="slip-card"
        >
          <div class="card-top">
            <div class="card-meta">
              <span class="match-name">{{ item.matchName }}</span>
              <span class="card-market">{{ item.marketName }}</span>
            </div>
            <div class="card-actions">
              <button type="button" class="del-btn" @click="removeItem(item.selectionId)">✕</button>
              <button
                type="button"
                class="locate-btn"
                :title="t('bet.slip_locate')"
                :aria-label="t('bet.slip_locate_aria')"
                @click="locateSlipItem(item)"
              >
                <svg class="locate-icon" viewBox="0 0 24 24" aria-hidden="true">
                  <path
                    d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5A2.5 2.5 0 1 1 12 6a2.5 2.5 0 0 1 0 5.5z"
                    fill="currentColor"
                  />
                </svg>
              </button>
            </div>
          </div>
          <div class="card-selection-row">
            <span class="selection-name">{{ item.selectionName }}</span>
            <span class="odds-value">
              <template v-if="oddsDeltaFor(item.selectionId)">
                <span class="odds-change" :class="oddsTrendClass(oddsDeltaFor(item.selectionId)!)">
                  {{ oddsDeltaFor(item.selectionId)!.oldOdds.toFixed(2) }} →
                  {{ oddsDeltaFor(item.selectionId)!.newOdds.toFixed(2) }}
                </span>
              </template>
              <template v-else>{{ item.odds.toFixed(2) }}</template>
            </span>
          </div>

          <div v-if="activeTab === 'single'" class="card-stake-block">
            <div class="card-stake-inline">
              <div class="card-stake-input-box">
                <input
                  :value="itemStakeInputValue(item.selectionId)"
                  type="number"
                  class="card-stake-input"
                  :placeholder="t('bet.stake_placeholder') || '金额'"
                  @input="onItemStakeInput(item.selectionId, $event)"
                />
                <button
                  v-if="itemStakeInputValue(item.selectionId)"
                  type="button"
                  class="clear-input-btn"
                  @click="clearItemStakeInput(item.selectionId)"
                >
                  ✕
                </button>
              </div>
              <button type="button" class="chip-btn mini" @click="addItemStake(item.selectionId, 50)">+50</button>
              <button type="button" class="chip-btn mini" @click="addItemStake(item.selectionId, 100)">+100</button>
              <button type="button" class="chip-btn mini" @click="setItemMaxStake(item.selectionId)">
                {{ t('bet.stake_max') || '全' }}
              </button>
            </div>
            <div
              class="card-action-row"
              :class="{
                'card-action-row--single': item.allowParlay === false || item.allowSingle === false,
              }"
            >
              <button
                v-if="item.allowSingle !== false"
                type="button"
                class="card-action-btn card-single-btn"
                :disabled="loading || !canPlaceSingleItem(item)"
                @click="onPlaceSingleItemClick(item)"
              >
                {{ t('bet.slip_place_single') }}
              </button>
              <button
                v-if="item.allowParlay !== false"
                type="button"
                class="card-action-btn card-parlay-btn"
                :class="{ active: isItemInParlay(item) }"
                @click="addItemToParlay(item)"
              >
                <span class="card-parlay-btn-icon" aria-hidden="true">{{ isItemInParlay(item) ? '✓' : '+' }}</span>
                <span>{{ isItemInParlay(item) ? t('bet.slip_parlay_added_btn') : t('bet.add_to_parlay') }}</span>
              </button>
            </div>
            <div class="card-est-row">
              <span class="est-lbl">{{ t('bet.slip_est_return') }}</span>
              <span class="est-val">{{ formatMoney(itemEstReturn(item), locale) }}</span>
            </div>
          </div>
        </div>
        <div v-if="activeTab === 'parlay' && slip.parlayItems.length" class="parlay-summary">
          <span>{{ t('bet.slip_parlay_count', { n: slip.parlayItems.length }) }}</span>
          <span class="parlay-odds">{{ t('bet.slip_parlay_odds', { odds: totalOddsText }) }}</span>
        </div>
      </TransitionGroup>

      <!-- Stake input (parlay only) -->
      <div v-if="activeTab === 'parlay' && activeItems.length" class="stake-control">
        <div class="stake-input-box">
            <span class="currency">{{ t('bet.slip_currency') || 'CNY' }}</span>
            <input
              v-model="stakeInput"
              type="number"
              class="stake-input"
              :placeholder="t('bet.stake') || '输入金额'"
              @input="onStakeInput"
            />
            <button v-if="stakeInput" type="button" class="clear-input-btn" @click="clearStakeInput">✕</button>
          </div>

          <div class="quick-chips">
            <button type="button" class="chip-btn" @click="addStake(50)">+50</button>
            <button type="button" class="chip-btn" @click="addStake(100)">+100</button>
            <button type="button" class="chip-btn" @click="addStake(500)">+500</button>
            <button type="button" class="chip-btn" @click="setMaxStake">{{ t('bet.stake_max') || '最大' }}</button>
          </div>
      </div>

      <p v-if="error" class="panel-error-msg">{{ error }}</p>
      <p v-if="success" class="panel-success-msg">{{ success }}</p>
      </template>
    </div>

    <div v-if="activeTab === 'history'" class="panel-bottom-fixed panel-bottom-history">
      <RouterLink to="/bets" class="history-view-all-btn">
        {{ t('bet.slip_history_view_all') }}
      </RouterLink>
    </div>

    <div v-else-if="activeItems.length" class="panel-bottom-fixed">
      <div class="panel-summary">
        <div class="summary-row">
          <span class="lbl">{{ t('bet.slip_total_stake') }}</span>
          <span class="val">{{ stakeText }}</span>
        </div>
        <div class="summary-row est-return">
          <span class="lbl">{{ t('bet.slip_est_return') }}</span>
          <span class="val">{{ estimatedReturnText }}</span>
        </div>
      </div>
      <div class="panel-foot">
        <button type="button" class="clear-slip-btn foot-clear" @click="clearActiveSlip">
          {{ t('bet.slip_clear') }}
        </button>
        <button
          type="button"
          class="submit-btn"
          :disabled="loading || !canSubmitWithOdds"
          @click="onPlaceBetClick"
        >
          {{ submitButtonLabel }}
        </button>
      </div>
    </div>

    <ConfirmDialog
      v-model:visible="showPlaceConfirm"
      :title="t('bet.place_confirm_title')"
      :message="placeConfirmMessage"
      :confirm-text="t('bet.place_bet')"
      :cancel-text="t('common.cancel')"
      :loading="loading"
      @confirm="confirmPlaceBet"
    />

    <!-- Success Overlay -->
    <BetSuccessOverlay :show="showSuccess" @done="onSuccessDone" />
  </div>
</template>

<style scoped>
.betslip-panel {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--bg-card);
}

.panel-head {
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.panel-head h3 {
  margin: 0;
  font-size: 14px;
  font-weight: 800;
  color: var(--primary-light);
}

.clear-slip-btn {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 3px 8px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}

.clear-slip-btn.foot-clear {
  flex: 0 0 20%;
  min-width: 0;
  padding: 10px 4px;
  font-size: 12px;
  cursor: pointer;
}

.clear-slip-btn:hover {
  color: var(--primary-light);
  border-color: var(--border-active);
  background: rgba(0, 102, 204, 0.06);
}

.balance-info {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
}

.balance-info .lbl {
  color: var(--text-muted);
}

.balance-info .val {
  color: var(--text);
  font-weight: 700;
}

.refresh-btn {
  background: transparent;
  color: var(--primary-light);
  font-size: 12px;
  line-height: 1;
  padding: 0 2px;
}

.panel-tabs {
  display: flex;
  border-bottom: 1px solid var(--border);
}

.tab-btn {
  flex: 1;
  background: transparent;
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 700;
  padding: 10px 4px;
  border-bottom: 2px solid transparent;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

.tab-btn:hover {
  color: var(--text);
}

.tab-btn.active {
  color: var(--primary-light);
  border-bottom-color: var(--primary);
}

.badge {
  background: var(--primary);
  color: var(--bg-card);
  font-size: 9px;
  font-weight: 800;
  padding: 1px 4px;
  border-radius: 6px;
  line-height: 1;
}

.odds-warning {
  background: rgba(0, 102, 204, 0.12);
  border-bottom: 1px solid var(--border-active);
  color: var(--primary-light);
  font-size: 11px;
  font-weight: 600;
  padding: 8px 16px;
  margin: 0;
  text-align: center;
}

.panel-body {
  flex: 1;
  overflow-y: auto;
  padding: 10px 12px;
  min-height: 0;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 60%;
  color: var(--text-muted);
  gap: 12px;
  text-align: center;
  font-size: 12px;
}

.empty-icon {
  width: 32px;
  height: 32px;
  opacity: 0.3;
}

.items-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 10px;
}

.slip-card {
  background: var(--bg-body);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 7px 8px;
  position: relative;
  transition: border-color 0.2s;
}

.slip-card:hover {
  border-color: var(--border-active);
}

.card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 6px;
  margin-bottom: 4px;
}

.card-meta {
  min-width: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.match-name {
  font-size: 10px;
  font-weight: 700;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-market {
  font-size: 10px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.del-btn {
  background: transparent;
  color: var(--text-muted);
  font-size: 10px;
  padding: 0 2px;
  line-height: 1;
}

.card-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

.locate-btn {
  background: transparent;
  color: var(--text-muted);
  padding: 0 2px;
  line-height: 1;
  display: flex;
}

.locate-btn:hover {
  color: var(--primary-light);
}

.locate-icon {
  width: 12px;
  height: 12px;
}

.del-btn:hover {
  color: var(--danger);
}

.card-selection-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-bottom: 2px;
}

.selection-name {
  font-size: 11px;
  font-weight: 700;
  color: var(--text);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.odds-value {
  font-size: 12px;
  font-weight: 800;
  color: var(--primary-light);
  flex-shrink: 0;
}

.odds-change {
  font-size: 11px;
  padding: 1px 4px;
  border-radius: 2px;
}

.odds-up {
  background: rgba(46, 204, 113, 0.15);
  color: #2ecc71;
}

.odds-down {
  background: rgba(231, 76, 60, 0.15);
  color: #e74c3c;
}

.odds-suspended {
  background: rgba(0, 0, 0, 0.04);
  color: var(--text-muted);
  text-decoration: line-through;
}

.card-stake-block {
  margin-top: 5px;
  padding-top: 5px;
  border-top: 1px solid rgba(0, 0, 0, 0.04);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.card-stake-inline {
  display: flex;
  align-items: center;
  gap: 4px;
}

.card-stake-input-box {
  flex: 1;
  display: flex;
  align-items: center;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 6px;
  height: 26px;
  min-width: 0;
}

.card-stake-input {
  flex: 1;
  background: transparent;
  border: none !important;
  color: var(--text);
  padding: 0;
  font-size: 11px;
  font-weight: 700;
  min-width: 0;
}

.card-stake-input::-webkit-outer-spin-button,
.card-stake-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.chip-btn.mini {
  flex: 0 0 auto;
  padding: 0 6px;
  height: 26px;
  font-size: 9px;
  min-width: 32px;
}

.card-action-row {
  display: flex;
  gap: 6px;
  margin-top: 6px;
}

.card-action-row--single .card-action-btn {
  flex: 1 1 100%;
}

.card-action-btn {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 6px 6px;
  border-radius: 4px;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.2;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s;
}

.card-action-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.card-single-btn {
  border: 1px solid rgba(0, 102, 204, 0.55);
  background: rgba(0, 102, 204, 0.14);
  color: var(--primary-light);
}

.card-single-btn:hover:not(:disabled) {
  background: rgba(0, 102, 204, 0.22);
  border-color: var(--border-active);
  color: var(--text);
}

.card-parlay-btn {
  border: 1px dashed rgba(0, 102, 204, 0.45);
  background: rgba(0, 102, 204, 0.04);
  color: var(--primary-light);
}

.card-parlay-btn:hover:not(:disabled) {
  background: rgba(0, 102, 204, 0.1);
  border-color: var(--border-active);
  color: var(--text);
}

.card-parlay-btn.active {
  border-style: solid;
  border-color: rgba(0, 102, 204, 0.35);
  background: rgba(0, 0, 0, 0.04);
  color: var(--text-muted);
}

.card-parlay-btn.active:hover {
  color: var(--primary-light);
  border-color: var(--border-active);
}

.card-parlay-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: rgba(0, 102, 204, 0.16);
  font-size: 11px;
  line-height: 1;
  font-weight: 800;
}

.card-parlay-btn.active .card-parlay-btn-icon {
  background: rgba(46, 204, 113, 0.18);
  color: #2ecc71;
}

.card-est-row {
  display: flex;
  justify-content: space-between;
  font-size: 9px;
  line-height: 1.2;
}

.card-est-row .est-lbl {
  color: var(--text-muted);
}

.card-est-row .est-val {
  color: var(--primary-light);
  font-weight: 800;
}

.panel-warning {
  font-size: 11px;
  color: var(--primary-light);
  background: rgba(0, 102, 204, 0.08);
  border: 1px solid var(--border-active);
  border-radius: 4px;
  padding: 8px;
  margin: 6px 0;
}

.parlay-summary {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
  padding: 4px 6px;
  background: var(--bg-body);
  border-radius: 4px;
}

.parlay-odds {
  color: var(--primary-light);
}

.stake-control {
  background: var(--bg-body);
  border-radius: 4px;
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.stake-input-box {
  display: flex;
  align-items: center;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0 10px;
  height: 36px;
}

.currency {
  font-size: 11px;
  font-weight: 700;
  color: var(--primary-light);
  margin-right: 6px;
}

.stake-input {
  flex: 1;
  background: transparent;
  border: none !important;
  color: var(--text);
  padding: 0;
  font-size: 14px;
  font-weight: 700;
}

.stake-input::-webkit-outer-spin-button,
.stake-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.clear-input-btn {
  background: transparent;
  color: var(--text-muted);
  font-size: 12px;
  padding: 0 4px;
}

.quick-chips {
  display: flex;
  gap: 6px;
}

.chip-btn {
  flex: 1;
  background: var(--bg-body);
  border: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 700;
  padding: 5px 0;
  border-radius: 4px;
  transition: all 0.2s;
}

.chip-btn:hover {
  border-color: var(--border-active);
  color: var(--text);
}

.panel-error-msg {
  color: var(--danger);
  font-size: 11px;
  font-weight: 600;
  margin-top: 8px;
}

.panel-success-msg {
  color: #2ecc71;
  font-size: 11px;
  font-weight: 600;
  margin-top: 8px;
}

.panel-bottom-fixed {
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  background: var(--bg-card);
}

.panel-summary {
  padding: 10px 16px 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-bottom: 1px solid var(--border);
}

.summary-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 11px;
}

.summary-row .lbl {
  color: var(--text-muted);
}

.summary-row .val {
  color: var(--text);
  font-weight: 700;
}

.summary-row.est-return .val {
  color: var(--primary-light);
  font-size: 13px;
  font-weight: 800;
}

.panel-foot {
  padding: 10px 16px 12px;
  display: flex;
  align-items: stretch;
  gap: 8px;
}

.add-parlay-btn {
  width: 100%;
  padding: 8px;
  border-radius: 4px;
  border: 1px solid var(--border-active);
  background: transparent;
  color: var(--primary-light);
  font-size: 11px;
  font-weight: 700;
  transition: all 0.2s;
}

.add-parlay-btn:hover {
  background: rgba(0, 102, 204, 0.05);
  border-color: var(--primary);
}

.submit-btn {
  flex: 1;
  min-width: 0;
  padding: 10px;
  border-radius: 4px;
  background: linear-gradient(180deg, var(--primary-light) 0%, var(--primary) 100%);
  color: #FFFFFF;
  font-size: 13px;
  font-weight: 800;
  text-transform: uppercase;
  transition: opacity 0.2s;
  border: none;
  cursor: pointer;
}

.submit-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  filter: saturate(0.5);
}

.history-view-all-btn {
  display: block;
  width: 100%;
  padding: 10px;
  border-radius: 4px;
  border: 1px solid var(--border-active);
  background: rgba(0, 102, 204, 0.08);
  color: var(--primary-light);
  font-size: 13px;
  font-weight: 800;
  text-align: center;
  text-decoration: none;
  transition: background 0.15s, border-color 0.15s;
}

.history-view-all-btn:hover {
  background: rgba(0, 102, 204, 0.14);
  border-color: var(--primary);
}

.panel-bottom-history {
  padding: 10px 16px 12px;
}

.history-scope-bar {
  display: flex;
  gap: 6px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
}

.scope-chip {
  flex: 1;
  padding: 5px 8px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 700;
  transition: all 0.15s;
}

.scope-chip.active {
  color: var(--primary-light);
  border-color: var(--border-active);
  background: rgba(0, 102, 204, 0.08);
}

.history-loading {
  display: flex;
  justify-content: center;
  padding: 32px 0;
}

.history-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.history-card {
  display: block;
  padding: 8px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--bg-body);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s, background 0.15s;
}

.history-card:hover {
  border-color: var(--border-active);
  background: rgba(0, 102, 204, 0.04);
}

.history-card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
}

.history-pick {
  font-size: 11px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.35;
  min-width: 0;
}

.history-status {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 800;
  padding: 1px 5px;
  border-radius: 3px;
}

.history-status.hist-won {
  color: #2ecc71;
  background: rgba(46, 204, 113, 0.12);
}

.history-status.hist-lost {
  color: #e74c3c;
  background: rgba(231, 76, 60, 0.12);
}

.history-status.hist-push {
  color: #95a5a6;
  background: rgba(149, 165, 166, 0.12);
}

.history-status.hist-pending {
  color: var(--primary-light);
  background: rgba(0, 102, 204, 0.1);
}

.history-match {
  font-size: 10px;
  color: var(--text-muted);
  margin-bottom: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.history-card-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  font-size: 11px;
}

.history-stake {
  color: var(--text-muted);
}

.history-return {
  font-weight: 800;
}

.history-return.hist-won {
  color: #2ecc71;
}

.history-return.hist-lost {
  color: #e74c3c;
}

.history-return.hist-push,
.history-return.hist-pending {
  color: var(--primary-light);
}

.history-meta {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 4px;
  font-size: 9px;
  color: var(--text-muted);
  opacity: 0.85;
}

.history-load-more {
  width: 100%;
  margin-top: 8px;
  padding: 8px;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--primary-light);
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
}

.history-load-more:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.list-slide-enter-active,
.list-slide-leave-active {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.list-slide-enter-from {
  opacity: 0;
  transform: translateY(-20px) scale(0.95);
}

.list-slide-leave-to {
  opacity: 0;
  transform: translateY(20px) scale(0.95);
}

.list-slide-move {
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>
