import { uiLocale } from '@/utils/uiLocale';
import { useState, useCallback, useEffect, useRef } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { wheelApi, type WheelPrize, type SpinResult, type SpinHistoryItem } from '../api/wheel';
import FortuneWheel from '../components/wheel/FortuneWheel';
import WheelLegend from '../components/wheel/WheelLegend';
import { usePlatform, useHaptic } from '@/platform';
import { useNotify } from '@/platform/hooks/useNotify';
import { Card } from '@/components/data-display/Card/Card';
import { Button } from '@/components/primitives/Button/Button';
import { motion, AnimatePresence } from 'framer-motion';
import { staggerContainer, staggerItem } from '@/components/motion/transitions';
import { PiCaretDown } from 'react-icons/pi';
import { StarIcon, CalendarIcon, HistoryIcon, CloseIcon } from '@/components/icons';
import { cn } from '@/lib/utils';

// Icons
const ChevronIcon = ({ expanded }: { expanded: boolean }) => (
  <PiCaretDown
    className={cn('h-5 w-5 transition-transform duration-200', expanded && 'rotate-180')}
  />
);

/**
 * Rotation (mod 360) that brings sector `prizeIndex` under the top pointer.
 * Mirrors the backend _calculate_rotation logic in wheel_service.py.
 */
function rotationForIndex(prizes: WheelPrize[], prizeIndex: number): number {
  if (prizes.length === 0) return 0;
  const sectorAngle = 360 / prizes.length;
  const baseAngle = prizeIndex * sectorAngle + sectorAngle / 2;
  const offset = (Math.random() - 0.5) * sectorAngle * 0.6; // ±30% within the sector
  return 360 - baseAngle + offset;
}

/** Index of the "Nothing" sector, or 0 if there isn't one. */
function neutralIndex(prizes: WheelPrize[]): number {
  const idx = prizes.findIndex((p) => p.prize_type === 'nothing');
  return idx === -1 ? 0 : idx;
}

/**
 * Rotation that lands the wheel on the ACTUAL won prize's sector.
 *
 * Matches by prize_id (exact). It must NEVER land on a random sector: a random
 * angle was the root cause of "the wheel shows месяц/50₽ but the result is
 * Ничего" on the browser Stars path — when the prize couldn't be located the old
 * code span to Math.random()*360, which often pointed at a winning slot. When the
 * prize can't be resolved we land on the neutral ("Nothing") sector instead, so
 * the animation never falsely celebrates a win.
 */
function calculateRotationForPrize(prizes: WheelPrize[], result: SpinResult): number {
  let prizeIndex = result.prize_id != null ? prizes.findIndex((p) => p.id === result.prize_id) : -1;

  // Defensive fallbacks for older payloads without prize_id: name+emoji, then name.
  if (prizeIndex === -1 && result.prize_display_name) {
    prizeIndex = prizes.findIndex(
      (p) => p.display_name === result.prize_display_name && p.emoji === result.emoji,
    );
    if (prizeIndex === -1) {
      prizeIndex = prizes.findIndex((p) => p.display_name === result.prize_display_name);
    }
  }

  if (prizeIndex === -1) prizeIndex = neutralIndex(prizes); // unknown → neutral, never random

  return rotationForIndex(prizes, prizeIndex);
}

/**
 * Rotation used when the real result is unknown (e.g. the Stars-payment poll timed
 * out). Lands on the neutral ("Nothing") sector — never a random/winning angle.
 */
function neutralRotation(prizes: WheelPrize[]): number {
  return rotationForIndex(prizes, neutralIndex(prizes));
}

export default function Wheel() {
  const { t } = useTranslation();
  const queryClient = useQueryClient();
  const { openInvoice, capabilities } = usePlatform();
  const haptic = useHaptic();
  const notify = useNotify();

  const [isSpinning, setIsSpinning] = useState(false);
  const [targetRotation, setTargetRotation] = useState<number | null>(null);
  const [spinResult, setSpinResult] = useState<SpinResult | null>(null);
  const [paymentType, setPaymentType] = useState<'telegram_stars' | 'subscription_days'>(
    'telegram_stars',
  );
  const [isPayingStars, setIsPayingStars] = useState(false);
  const [historyExpanded, setHistoryExpanded] = useState(false);
  const [showStarsConfirm, setShowStarsConfirm] = useState(false);
  const [selectedSubscriptionId, setSelectedSubscriptionId] = useState<number | null>(null);
  const paymentTypeInitialized = useRef(false);

  const {
    data: config,
    isLoading,
    error,
  } = useQuery({
    queryKey: ['wheel-config'],
    queryFn: wheelApi.getConfig,
  });

  const { data: history } = useQuery({
    queryKey: ['wheel-history'],
    queryFn: () => wheelApi.getHistory(1, 10),
  });

  // Auto-select payment type based on availability (only on initial load)
  useEffect(() => {
    if (!config || paymentTypeInitialized.current) return;
    paymentTypeInitialized.current = true;

    const starsEnabled = config.spin_cost_stars_enabled && config.spin_cost_stars;
    const daysEnabled = config.spin_cost_days_enabled && config.spin_cost_days;

    if (starsEnabled) {
      setPaymentType('telegram_stars');
    } else if (daysEnabled) {
      setPaymentType('subscription_days');
    }

    // Auto-select subscription if only one eligible
    if (config.eligible_subscriptions?.length === 1) {
      setSelectedSubscriptionId(config.eligible_subscriptions[0].id);
    }
  }, [config]);

  // Function to poll for new spin result after Stars payment
  const pollForSpinResult = useCallback(
    async (signal: AbortSignal, maxAttempts = 15, delayMs = 800) => {
      // Wait a bit before first poll to give the bot time to process the payment
      await new Promise((resolve) => setTimeout(resolve, 1500));

      if (signal.aborted) return null;

      // Get current history to find the latest spin ID
      let historyBefore;
      try {
        historyBefore = await wheelApi.getHistory(1, 1);
      } catch {
        historyBefore = { items: [], total: 0 };
      }
      const lastSpinIdBefore = historyBefore.items.length > 0 ? historyBefore.items[0].id : 0;

      for (let attempt = 0; attempt < maxAttempts; attempt++) {
        if (signal.aborted) return null;

        await new Promise((resolve) => setTimeout(resolve, delayMs));

        if (signal.aborted) return null;

        try {
          const historyAfter = await wheelApi.getHistory(1, 1);

          // Check if we have a new spin (either new item or higher ID)
          if (historyAfter.items.length > 0) {
            const latestSpin = historyAfter.items[0];
            // If we had no spins before, or this spin has a higher ID
            if (lastSpinIdBefore === 0 || latestSpin.id > lastSpinIdBefore) {
              // Found a new spin! Return it as SpinResult
              return {
                success: true,
                // WheelPrize id — lets the wheel land on the exact won sector.
                // (Was latestSpin.id, the SPIN id, which never matched a sector and
                // forced the random-angle fallback → the fake-win bug.)
                prize_id: latestSpin.prize_id,
                prize_type: latestSpin.prize_type,
                prize_value: latestSpin.prize_value,
                prize_display_name: latestSpin.prize_display_name,
                emoji: latestSpin.emoji,
                color: latestSpin.color,
                rotation_degrees: 0, // Not needed for result display
                message:
                  latestSpin.prize_type === 'nothing'
                    ? t('wheel.noPrize')
                    : `${t('wheel.youWon')} ${latestSpin.prize_display_name}!`,
                promocode: null, // Promocode is sent to bot chat
                error: null,
              } as SpinResult;
            }
          }
        } catch {
          // Continue polling on error
        }
      }

      // Timeout - couldn't find new spin
      return null;
    },
    [t],
  );

  // Ref to store pending Stars payment result
  const pendingStarsResultRef = useRef<SpinResult | null>(null);
  const isStarsSpinRef = useRef(false);
  const pollingAbortRef = useRef<AbortController | null>(null);
  const preOpenedWindowRef = useRef<Window | null>(null);

  // Cleanup polling on unmount
  useEffect(() => {
    return () => {
      if (pollingAbortRef.current) {
        pollingAbortRef.current.abort();
      }
    };
  }, []);

  const starsInvoiceMutation = useMutation({
    mutationFn: wheelApi.createStarsInvoice,
    onSuccess: async (data) => {
      // Use platform's openInvoice if available
      if (capabilities.hasInvoice) {
        const status = await openInvoice(data.invoice_url);

        if (status === 'paid') {
          // Mark this as a Stars spin so handleSpinComplete knows to use the pending result
          isStarsSpinRef.current = true;
          pendingStarsResultRef.current = null;

          // Cancel any existing polling
          if (pollingAbortRef.current) {
            pollingAbortRef.current.abort();
          }
          pollingAbortRef.current = new AbortController();

          // Keep isPayingStars=true to show loading state while polling for result.
          // We poll FIRST to get the actual prize, then calculate the correct
          // rotation angle so the wheel visually lands on the right sector.
          const abortSignal = pollingAbortRef.current.signal;
          pollForSpinResult(abortSignal)
            .then((result) => {
              if (abortSignal.aborted) return;

              queryClient.invalidateQueries({ queryKey: ['wheel-config'] });
              queryClient.invalidateQueries({ queryKey: ['wheel-history'] });

              setIsPayingStars(false);

              if (result) {
                pendingStarsResultRef.current = result;
                // Land on the ACTUAL won sector (matched by prize_id).
                setTargetRotation(calculateRotationForPrize(config?.prizes ?? [], result));
              } else {
                // Couldn't get the result — land on the neutral sector (never a
                // random/winning angle) and tell the user to check their history.
                pendingStarsResultRef.current = {
                  success: true,
                  prize_id: null,
                  prize_type: null,
                  prize_value: 0,
                  prize_display_name: '',
                  emoji: '🎰',
                  color: '#8B5CF6',
                  rotation_degrees: 0,
                  message: t('wheel.starsPaymentSuccessCheckHistory'),
                  promocode: null,
                  error: null,
                };
                setTargetRotation(neutralRotation(config?.prizes ?? []));
              }

              setIsSpinning(true);
            })
            .catch(() => {
              if (abortSignal.aborted) return;

              setIsPayingStars(false);

              // Error polling — land on the neutral sector (never a random/winning
              // angle) and show the generic "check your history" message.
              pendingStarsResultRef.current = {
                success: true,
                prize_id: null,
                prize_type: null,
                prize_value: 0,
                prize_display_name: '',
                emoji: '🎰',
                color: '#8B5CF6',
                rotation_degrees: 0,
                message: t('wheel.starsPaymentSuccessCheckHistory'),
                promocode: null,
                error: null,
              };
              setTargetRotation(neutralRotation(config?.prizes ?? []));
              setIsSpinning(true);
            });
        } else if (status !== 'cancelled') {
          setIsPayingStars(false);
          setSpinResult({
            success: false,
            prize_id: null,
            prize_type: null,
            prize_value: 0,
            prize_display_name: '',
            emoji: '😔',
            color: '#EF4444',
            rotation_degrees: 0,
            message: t('wheel.starsPaymentFailed'),
            promocode: null,
            error: 'payment_failed',
          });
        } else {
          setIsPayingStars(false);
        }
      } else {
        // Fallback: redirect pre-opened window to invoice URL
        setIsPayingStars(false);
        if (preOpenedWindowRef.current) {
          preOpenedWindowRef.current.location.href = data.invoice_url;
          preOpenedWindowRef.current = null;
        }
        setSpinResult({
          success: true,
          prize_id: null,
          prize_type: null,
          prize_value: 0,
          prize_display_name: '',
          emoji: '⭐',
          color: '#8B5CF6',
          rotation_degrees: 0,
          message: t('wheel.starsPaymentRedirected'),
          promocode: null,
          error: null,
        });
      }
    },
    onError: () => {
      setIsPayingStars(false);
      if (preOpenedWindowRef.current) {
        preOpenedWindowRef.current.close();
        preOpenedWindowRef.current = null;
      }
      setSpinResult({
        success: false,
        prize_id: null,
        prize_type: null,
        prize_value: 0,
        prize_display_name: '',
        emoji: '😔',
        color: '#EF4444',
        rotation_degrees: 0,
        message: t('wheel.errors.networkError'),
        promocode: null,
        error: 'network_error',
      });
    },
  });

  const handleDirectStarsPay = () => {
    setShowStarsConfirm(false);
    setIsPayingStars(true);
    // In browser: pre-open window synchronously (direct user gesture) to avoid popup blocker
    if (!capabilities.hasInvoice) {
      // Web-only: synchronously pre-open a tab during the user gesture to dodge the
      // popup blocker before the async invoice URL resolves. Not reached in Telegram
      // (hasInvoice is true there, so the native invoice flow is used instead).
      // biome-ignore lint: canonical popup-blocker workaround, see comment above
      preOpenedWindowRef.current = window.open('about:blank', '_blank') || null;
    }
    starsInvoiceMutation.mutate();
  };

  const spinMutation = useMutation({
    mutationFn: () => wheelApi.spin(paymentType, selectedSubscriptionId ?? undefined),
    onSuccess: (result) => {
      if (result.success) {
        setTargetRotation(result.rotation_degrees);
        setSpinResult(result);
      } else {
        setIsSpinning(false);
        setSpinResult(result);
      }
    },
    onError: () => {
      setIsSpinning(false);
      setSpinResult({
        success: false,
        message: t('wheel.errors.networkError'),
        error: 'network_error',
        prize_id: null,
        prize_type: null,
        prize_value: 0,
        prize_display_name: '',
        emoji: '',
        color: '',
        rotation_degrees: 0,
        promocode: null,
      });
    },
  });

  const handleSpin = () => {
    if (!config?.can_spin || isSpinning) return;
    setIsSpinning(true);
    spinMutation.mutate();
  };

  const handleUnifiedSpin = () => {
    if (noSubscription) return;
    if (paymentType === 'telegram_stars') {
      if (!config?.spin_cost_stars_enabled || !config?.spin_cost_stars) {
        notify.warning(t('wheel.starsNotAvailable'));
        return;
      }
      setShowStarsConfirm(true);
    } else {
      handleSpin();
    }
  };

  const handleSpinComplete = useCallback(() => {
    setIsSpinning(false);

    // Check if this was a Stars payment spin
    if (isStarsSpinRef.current) {
      isStarsSpinRef.current = false;

      // Use the pending result from polling, or show a fallback
      if (pendingStarsResultRef.current) {
        setSpinResult(pendingStarsResultRef.current);
        if (pendingStarsResultRef.current.prize_type === 'nothing') {
          haptic.notification('warning');
        } else {
          haptic.notification('success');
        }
        pendingStarsResultRef.current = null;
      } else {
        // Polling still in progress or failed - show fallback
        setSpinResult({
          success: true,
          prize_id: null,
          prize_type: null,
          prize_value: 0,
          prize_display_name: '',
          emoji: '🎰',
          color: '#8B5CF6',
          rotation_degrees: 0,
          message: t('wheel.starsPaymentSuccessCheckHistory'),
          promocode: null,
          error: null,
        });
        haptic.notification('success');
      }
    } else if (spinResult) {
      // Regular spin - haptic based on result
      if (spinResult.success && spinResult.prize_type !== 'nothing') {
        haptic.notification('success');
      } else {
        haptic.notification('warning');
      }
    }

    queryClient.invalidateQueries({ queryKey: ['wheel-config'] });
    queryClient.invalidateQueries({ queryKey: ['wheel-history'] });
  }, [queryClient, t, haptic, spinResult]);

  const closeResultModal = () => {
    setSpinResult(null);
    setTargetRotation(null);
  };

  if (isLoading) {
    return (
      <div className="flex min-h-[60vh] items-center justify-center">
        <div className="h-12 w-12 animate-spin rounded-full border-2 border-accent-500 border-t-transparent" />
      </div>
    );
  }

  if (error || !config) {
    return (
      <div className="flex min-h-[60vh] flex-col items-center justify-center gap-4">
        <div className="flex h-20 w-20 items-center justify-center rounded-full bg-error-500/10">
          <span className="text-4xl">😔</span>
        </div>
        <p className="text-lg text-dark-400">{t('wheel.errors.loadFailed')}</p>
      </div>
    );
  }

  if (!config.is_enabled) {
    return (
      <div className="flex min-h-[60vh] flex-col items-center justify-center gap-6">
        <div className="flex h-24 w-24 items-center justify-center rounded-full bg-dark-800">
          <span className="text-5xl">🎡</span>
        </div>
        <div className="text-center">
          <h1 className="mb-2 text-2xl font-bold text-dark-100">{t('wheel.title')}</h1>
          <p className="text-dark-400">{t('wheel.disabled')}</p>
        </div>
      </div>
    );
  }

  const starsEnabled = config.spin_cost_stars_enabled && config.spin_cost_stars;
  const daysEnabled = config.spin_cost_days_enabled && config.spin_cost_days;
  const bothMethodsAvailable = !!(starsEnabled && daysEnabled);

  // Stars via Telegram invoice don't require ruble balance, so only check daily limit
  const dailyLimitReached = config.daily_limit > 0 && config.user_spins_today >= config.daily_limit;
  const noSubscription = !config.has_subscription;
  const needsSubscriptionPick =
    paymentType === 'subscription_days' &&
    config.eligible_subscriptions &&
    config.eligible_subscriptions.length > 1 &&
    !selectedSubscriptionId;

  const spinDisabled =
    isSpinning ||
    isPayingStars ||
    dailyLimitReached ||
    noSubscription ||
    needsSubscriptionPick ||
    (paymentType === 'telegram_stars' ? !starsEnabled : !config.can_spin);

  return (
    <div className="animate-fade-in space-y-6 pb-8">
      {/* Simple Header */}
      <div>
        <h1 className="text-2xl font-bold text-dark-50">{t('wheel.title')}</h1>
        {config.daily_limit > 0 && (
          <p className="mt-1 text-dark-400">
            {t('wheel.spinsRemaining')}:{' '}
            <span className="inline-flex items-center rounded-full bg-accent-500/15 px-2 py-0.5 text-sm font-medium text-accent-400">
              {Math.max(0, config.daily_limit - config.user_spins_today)}/{config.daily_limit}
            </span>
          </p>
        )}
      </div>

      {/* Wheel Section */}
      <Card>
        <div className="grid gap-6 p-6 sm:p-8 lg:grid-cols-[1fr,280px]">
          {/* Left: Wheel and Controls */}
          <div>
            {/* Wheel */}
            <FortuneWheel
              prizes={config.prizes}
              isSpinning={isSpinning}
              targetRotation={targetRotation}
              onSpinComplete={handleSpinComplete}
            />

            {/* Spin Controls */}
            <div className="mt-8 space-y-4">
              {/* Payment type selector */}
              {(starsEnabled || daysEnabled) && (
                <div className="rounded-xl border border-dark-700/30 bg-dark-800/30 px-1 pb-1 pt-2">
                  <p className="mb-1 text-center text-xs text-dark-400">{t('wheel.spinCost')}</p>
                  <div
                    className={`grid gap-1 ${bothMethodsAvailable ? 'grid-cols-2' : 'grid-cols-1'}`}
                  >
                    {starsEnabled && (
                      <button
                        onClick={() => setPaymentType('telegram_stars')}
                        disabled={isSpinning}
                        className={`flex items-center justify-center gap-2 rounded-lg px-3 py-2.5 text-sm font-medium transition-all ${
                          paymentType === 'telegram_stars'
                            ? 'bg-accent-500/15 text-accent-400'
                            : 'text-dark-400 hover:text-dark-200'
                        }`}
                      >
                        <StarIcon />
                        {`${config.spin_cost_stars} ⭐`}
                      </button>
                    )}
                    {daysEnabled && (
                      <button
                        onClick={() => setPaymentType('subscription_days')}
                        disabled={isSpinning}
                        className={`flex items-center justify-center gap-2 rounded-lg px-3 py-2.5 text-sm font-medium transition-all ${
                          paymentType === 'subscription_days'
                            ? 'bg-accent-500/15 text-accent-400'
                            : 'text-dark-400 hover:text-dark-200'
                        }`}
                      >
                        <CalendarIcon />
                        {t('wheel.days', { count: config.spin_cost_days ?? 0 })}
                      </button>
                    )}
                  </div>
                </div>
              )}

              {/* Subscription selector for days payment in multi-tariff */}
              {paymentType === 'subscription_days' &&
                config.eligible_subscriptions &&
                config.eligible_subscriptions.length > 1 && (
                  <div className="rounded-xl border border-dark-700/30 bg-dark-800/30 p-3">
                    <p className="mb-2 text-center text-xs text-dark-400">
                      {t('wheel.selectSubscription', 'Выберите подписку')}
                    </p>
                    <div className="space-y-1.5">
                      {config.eligible_subscriptions.map((sub) => (
                        <button
                          key={sub.id}
                          onClick={() => setSelectedSubscriptionId(sub.id)}
                          disabled={isSpinning}
                          className={`flex w-full items-center justify-between gap-2 rounded-lg px-3 py-2 text-sm transition-all ${
                            selectedSubscriptionId === sub.id
                              ? 'bg-accent-500/15 text-accent-400'
                              : 'text-dark-400 hover:text-dark-200'
                          }`}
                        >
                          <span className="min-w-0 truncate font-medium">
                            {sub.tariff_name || t('subscription.defaultName', 'Подписка')}
                          </span>
                          <span className="shrink-0 text-xs opacity-60">
                            {sub.days_left} {t('common.units.days', 'дней')}
                          </span>
                        </button>
                      ))}
                    </div>
                  </div>
                )}

              {/* Stars confirmation panel */}
              {showStarsConfirm && !isSpinning && !isPayingStars ? (
                <div className="space-y-3 rounded-xl border border-accent-500/30 bg-accent-500/5 p-4">
                  <p className="text-center text-sm text-dark-300">
                    {t('wheel.confirmStarsPayment')}
                  </p>
                  <div className="grid grid-cols-2 gap-2">
                    <button
                      onClick={() => setShowStarsConfirm(false)}
                      className="rounded-lg border border-dark-700 bg-dark-800 px-4 py-2.5 text-sm font-medium text-dark-300 transition-colors hover:bg-dark-700"
                    >
                      {t('common.cancel')}
                    </button>
                    <button
                      onClick={handleDirectStarsPay}
                      className="rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600"
                    >
                      {t('wheel.payStars', { count: config.spin_cost_stars ?? 0 })}
                    </button>
                  </div>
                </div>
              ) : (
                /* Single Spin Button */
                <Button
                  variant="primary"
                  size="lg"
                  fullWidth
                  onClick={handleUnifiedSpin}
                  disabled={spinDisabled}
                  loading={isSpinning || isPayingStars}
                >
                  {isSpinning ? t('wheel.spinning') : t('wheel.spin')}
                </Button>
              )}

              {/* No subscription hint */}
              {!isSpinning && noSubscription && (
                <div className="rounded-linear border border-warning-500/30 bg-warning-500/5 p-4 text-center">
                  <p className="text-warning-400">{t('wheel.errors.noSubscription')}</p>
                </div>
              )}
              {/* Cannot spin hint — only show for days payment (Stars via invoice always works) */}
              {!isSpinning &&
                !noSubscription &&
                paymentType !== 'telegram_stars' &&
                !config.can_spin && (
                  <div className="rounded-linear border border-dark-700/30 bg-dark-800/30 p-4 text-center">
                    <p className="text-dark-400">
                      {config.can_spin_reason === 'daily_limit_reached'
                        ? t('wheel.errors.dailyLimitReached')
                        : t('wheel.errors.cannotSpin')}
                    </p>
                  </div>
                )}
              {/* Daily limit hint for Stars payment (not covered by can_spin check) */}
              {!isSpinning &&
                !noSubscription &&
                paymentType === 'telegram_stars' &&
                dailyLimitReached && (
                  <div className="rounded-linear border border-dark-700/30 bg-dark-800/30 p-4 text-center">
                    <p className="text-dark-400">{t('wheel.errors.dailyLimitReached')}</p>
                  </div>
                )}
              {/* Subscription selection required hint */}
              {!isSpinning && needsSubscriptionPick && (
                <div className="rounded-linear border border-warning-500/30 bg-warning-500/5 p-4 text-center">
                  <p className="text-warning-400">
                    {t('wheel.errors.selectSubscription', 'Выберите подписку для списания дней')}
                  </p>
                </div>
              )}

              {/* Inline Result Card */}
              {spinResult && !isSpinning && (
                <div
                  className={`animate-fade-in rounded-linear border p-4 ${
                    spinResult.success
                      ? 'border-accent-500/30 bg-accent-500/10'
                      : 'border-error-500/30 bg-error-500/10'
                  }`}
                >
                  <div className="flex items-center gap-3">
                    <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-linear bg-dark-700/50 text-2xl">
                      {spinResult.success ? spinResult.emoji || '🎉' : '😔'}
                    </div>
                    <div className="min-w-0 flex-1">
                      <div className="font-semibold text-dark-100">
                        {spinResult.success && spinResult.prize_display_name
                          ? spinResult.prize_display_name
                          : spinResult.success
                            ? spinResult.prize_type === 'nothing'
                              ? t('wheel.noLuck')
                              : t('wheel.congratulations')
                            : t('wheel.oops')}
                      </div>
                      <div className="text-sm text-dark-400">{spinResult.message}</div>
                    </div>
                    <button
                      onClick={closeResultModal}
                      className="shrink-0 rounded-lg p-2 text-dark-400 transition-colors hover:bg-white/5 hover:text-dark-200"
                    >
                      <CloseIcon className="h-6 w-6" />
                    </button>
                  </div>

                  {/* Promocode if won */}
                  {spinResult.promocode && (
                    <div className="mt-3 rounded-linear border border-accent-500/20 bg-accent-500/10 p-3 text-center">
                      <p className="mb-1 text-xs text-accent-400">{t('wheel.yourPromoCode')}</p>
                      <p className="select-all font-mono text-lg font-bold tracking-wider text-white">
                        {spinResult.promocode}
                      </p>
                    </div>
                  )}
                </div>
              )}
            </div>
            {/* End of left column: wheel and controls */}
          </div>

          {/* Right column (desktop) / Bottom (mobile): Prize Legend */}
          <div className="flex flex-col">
            <h3 className="mb-3 text-sm font-semibold text-dark-300">
              {t('wheel.prizes') || 'Призы'}
            </h3>
            <WheelLegend prizes={config.prizes} />
          </div>
        </div>
      </Card>

      {/* History Section - full width, collapsible */}
      <Card>
        <button
          onClick={() => setHistoryExpanded(!historyExpanded)}
          className="flex w-full items-center justify-between p-4"
        >
          <h3 className="flex items-center gap-2 font-semibold text-dark-100">
            <HistoryIcon />
            {t('wheel.recentSpins')}
            {history && history.items.length > 0 && (
              <span className="text-sm font-normal text-dark-500">({history.items.length})</span>
            )}
          </h3>
          <ChevronIcon expanded={historyExpanded} />
        </button>

        <AnimatePresence>
          {historyExpanded && (
            <motion.div
              initial={{ height: 0, opacity: 0 }}
              animate={{ height: 'auto', opacity: 1 }}
              exit={{ height: 0, opacity: 0 }}
              transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
              className="overflow-hidden"
            >
              <div className="border-t border-dark-700/30 px-4 pb-4 pt-2">
                {history && history.items.length > 0 ? (
                  // "hidden"/"show" don't exist in staggerContainer/staggerItem
                  // (their keys are initial/animate/exit), so the stagger here
                  // was silently a no-op
                  <motion.div
                    variants={staggerContainer}
                    initial="initial"
                    animate="animate"
                    className="space-y-2"
                  >
                    {history.items.map((item: SpinHistoryItem) => (
                      <motion.div
                        key={item.id}
                        variants={staggerItem}
                        className="flex items-center justify-between rounded-linear border border-dark-700/30 bg-dark-800/30 p-3"
                      >
                        <div className="flex items-center gap-3">
                          <div className="flex h-10 w-10 items-center justify-center rounded-linear bg-dark-700/50 text-xl">
                            {item.emoji}
                          </div>
                          <div className="min-w-0">
                            <div className="truncate text-sm font-medium text-dark-100">
                              {item.prize_display_name}
                            </div>
                            <div className="text-xs text-dark-500">
                              {new Date(item.created_at).toLocaleDateString(uiLocale())}
                            </div>
                          </div>
                        </div>
                        <div className="whitespace-nowrap text-sm text-dark-400">
                          -
                          {item.payment_type === 'telegram_stars'
                            ? `${item.payment_amount} ⭐`
                            : `${item.payment_amount}${t('wheel.days').charAt(0)}`}
                        </div>
                      </motion.div>
                    ))}
                  </motion.div>
                ) : (
                  <div className="py-6 text-center text-dark-500">
                    <div className="mb-2 text-3xl">🎰</div>
                    {t('wheel.noHistory')}
                  </div>
                )}
              </div>
            </motion.div>
          )}
        </AnimatePresence>
      </Card>
    </div>
  );
}
